mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 23:34:52 +02:00
8035712: Restore some of the RuntimeCallSite specializations
Reviewed-by: hannesw, lagergren
This commit is contained in:
parent
b5aaccb15f
commit
4a3782b060
11 changed files with 487 additions and 888 deletions
|
@ -105,33 +105,33 @@ final class BranchOptimizer {
|
||||||
|
|
||||||
case EQ:
|
case EQ:
|
||||||
case EQ_STRICT:
|
case EQ_STRICT:
|
||||||
codegen.loadBinaryOperands(binaryNode);
|
codegen.loadComparisonOperands(binaryNode);
|
||||||
method.conditionalJump(state ? EQ : NE, true, label);
|
method.conditionalJump(state ? EQ : NE, true, label);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case NE:
|
case NE:
|
||||||
case NE_STRICT:
|
case NE_STRICT:
|
||||||
codegen.loadBinaryOperands(binaryNode);
|
codegen.loadComparisonOperands(binaryNode);
|
||||||
method.conditionalJump(state ? NE : EQ, true, label);
|
method.conditionalJump(state ? NE : EQ, true, label);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case GE:
|
case GE:
|
||||||
codegen.loadBinaryOperands(binaryNode);
|
codegen.loadComparisonOperands(binaryNode);
|
||||||
method.conditionalJump(state ? GE : LT, false, label);
|
method.conditionalJump(state ? GE : LT, false, label);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case GT:
|
case GT:
|
||||||
codegen.loadBinaryOperands(binaryNode);
|
codegen.loadComparisonOperands(binaryNode);
|
||||||
method.conditionalJump(state ? GT : LE, false, label);
|
method.conditionalJump(state ? GT : LE, false, label);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case LE:
|
case LE:
|
||||||
codegen.loadBinaryOperands(binaryNode);
|
codegen.loadComparisonOperands(binaryNode);
|
||||||
method.conditionalJump(state ? LE : GT, true, label);
|
method.conditionalJump(state ? LE : GT, true, label);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case LT:
|
case LT:
|
||||||
codegen.loadBinaryOperands(binaryNode);
|
codegen.loadComparisonOperands(binaryNode);
|
||||||
method.conditionalJump(state ? LT : GE, true, label);
|
method.conditionalJump(state ? LT : GE, true, label);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -202,6 +202,12 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
private static final Call CREATE_FUNCTION_OBJECT_NO_SCOPE = CompilerConstants.staticCallNoLookup(ScriptFunctionImpl.class,
|
private static final Call CREATE_FUNCTION_OBJECT_NO_SCOPE = CompilerConstants.staticCallNoLookup(ScriptFunctionImpl.class,
|
||||||
"create", ScriptFunction.class, Object[].class, int.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;
|
private static final Class<?> ITERATOR_CLASS = Iterator.class;
|
||||||
static {
|
static {
|
||||||
assert ITERATOR_CLASS == CompilerConstants.ITERATOR_PREFIX.type();
|
assert ITERATOR_CLASS == CompilerConstants.ITERATOR_PREFIX.type();
|
||||||
|
@ -618,6 +624,104 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
return method;
|
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) {
|
private static final Type undefinedToNumber(final Type type) {
|
||||||
return type == Type.UNDEFINED ? Type.NUMBER : type;
|
return type == Type.UNDEFINED ? Type.NUMBER : type;
|
||||||
}
|
}
|
||||||
|
@ -628,6 +732,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
|
|
||||||
static final TypeBounds UNBOUNDED = new TypeBounds(Type.UNKNOWN, Type.OBJECT);
|
static final TypeBounds UNBOUNDED = new TypeBounds(Type.UNKNOWN, Type.OBJECT);
|
||||||
static final TypeBounds INT = exact(Type.INT);
|
static final TypeBounds INT = exact(Type.INT);
|
||||||
|
static final TypeBounds NUMBER = exact(Type.NUMBER);
|
||||||
static final TypeBounds OBJECT = exact(Type.OBJECT);
|
static final TypeBounds OBJECT = exact(Type.OBJECT);
|
||||||
static final TypeBounds BOOLEAN = exact(Type.BOOLEAN);
|
static final TypeBounds BOOLEAN = exact(Type.BOOLEAN);
|
||||||
|
|
||||||
|
@ -2756,15 +2861,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
newRuntimeNode = runtimeNode;
|
newRuntimeNode = runtimeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
new OptimisticOperation(newRuntimeNode, TypeBounds.UNBOUNDED) {
|
|
||||||
@Override
|
|
||||||
void loadStack() {
|
|
||||||
for (final Expression arg : args) {
|
for (final Expression arg : args) {
|
||||||
loadExpression(arg, TypeBounds.OBJECT);
|
loadExpression(arg, TypeBounds.OBJECT);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@Override
|
|
||||||
void consumeStack() {
|
|
||||||
method.invokestatic(
|
method.invokestatic(
|
||||||
CompilerConstants.className(ScriptRuntime.class),
|
CompilerConstants.className(ScriptRuntime.class),
|
||||||
newRuntimeNode.getRequest().toString(),
|
newRuntimeNode.getRequest().toString(),
|
||||||
|
@ -2773,8 +2873,6 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
false,
|
false,
|
||||||
newRuntimeNode.getType(),
|
newRuntimeNode.getType(),
|
||||||
args.size()).toString());
|
args.size()).toString());
|
||||||
}
|
|
||||||
}.emit();
|
|
||||||
|
|
||||||
method.convert(newRuntimeNode.getType());
|
method.convert(newRuntimeNode.getType());
|
||||||
}
|
}
|
||||||
|
@ -3989,8 +4087,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadCmp(final BinaryNode binaryNode, final Condition cond) {
|
private void loadCmp(final BinaryNode binaryNode, final Condition cond) {
|
||||||
assert comparisonOperandsArePrimitive(binaryNode) : binaryNode;
|
loadComparisonOperands(binaryNode);
|
||||||
loadBinaryOperands(binaryNode);
|
|
||||||
|
|
||||||
final Label trueLabel = new Label("trueLabel");
|
final Label trueLabel = new Label("trueLabel");
|
||||||
final Label afterLabel = new Label("skip");
|
final Label afterLabel = new Label("skip");
|
||||||
|
@ -4004,11 +4101,6 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||||
method.label(afterLabel);
|
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) {
|
private void loadMOD(final BinaryNode binaryNode, final TypeBounds resultBounds) {
|
||||||
new BinaryArith() {
|
new BinaryArith() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1359,8 +1359,6 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
|
||||||
final Expression lhs = binaryNode.lhs();
|
final Expression lhs = binaryNode.lhs();
|
||||||
final Expression rhs = binaryNode.rhs();
|
final Expression rhs = binaryNode.rhs();
|
||||||
|
|
||||||
Type cmpWidest = Type.widest(lhs.getType(), rhs.getType());
|
|
||||||
boolean newRuntimeNode = false, finalized = false;
|
|
||||||
final TokenType tt = binaryNode.tokenType();
|
final TokenType tt = binaryNode.tokenType();
|
||||||
switch (tt) {
|
switch (tt) {
|
||||||
case EQ_STRICT:
|
case EQ_STRICT:
|
||||||
|
@ -1373,14 +1371,12 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
|
||||||
}
|
}
|
||||||
// Specialize comparison of boolean with non-boolean
|
// Specialize comparison of boolean with non-boolean
|
||||||
if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
|
if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
|
||||||
newRuntimeNode = true;
|
return new RuntimeNode(binaryNode);
|
||||||
cmpWidest = Type.OBJECT;
|
|
||||||
finalized = true;
|
|
||||||
}
|
}
|
||||||
// fallthrough
|
// fallthrough
|
||||||
default:
|
default:
|
||||||
if (newRuntimeNode || cmpWidest.isObject()) {
|
if (lhs.getType().isObject() && rhs.getType().isObject()) {
|
||||||
return new RuntimeNode(binaryNode).setIsFinal(finalized);
|
return new RuntimeNode(binaryNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(binaryNode.isOptimisticUndecidedType()) {
|
} else if(binaryNode.isOptimisticUndecidedType()) {
|
||||||
|
|
|
@ -94,7 +94,6 @@ import jdk.nashorn.internal.ir.IdentNode;
|
||||||
import jdk.nashorn.internal.ir.JoinPredecessor;
|
import jdk.nashorn.internal.ir.JoinPredecessor;
|
||||||
import jdk.nashorn.internal.ir.LiteralNode;
|
import jdk.nashorn.internal.ir.LiteralNode;
|
||||||
import jdk.nashorn.internal.ir.LocalVariableConversion;
|
import jdk.nashorn.internal.ir.LocalVariableConversion;
|
||||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
|
||||||
import jdk.nashorn.internal.ir.Symbol;
|
import jdk.nashorn.internal.ir.Symbol;
|
||||||
import jdk.nashorn.internal.ir.TryNode;
|
import jdk.nashorn.internal.ir.TryNode;
|
||||||
import jdk.nashorn.internal.objects.NativeArray;
|
import jdk.nashorn.internal.objects.NativeArray;
|
||||||
|
@ -174,9 +173,6 @@ public class MethodEmitter implements Emitter {
|
||||||
/** Bootstrap for normal indy:s */
|
/** Bootstrap for normal indy:s */
|
||||||
private static final Handle LINKERBOOTSTRAP = new Handle(H_INVOKESTATIC, Bootstrap.BOOTSTRAP.className(), Bootstrap.BOOTSTRAP.name(), Bootstrap.BOOTSTRAP.descriptor());
|
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 */
|
/** Bootstrap for array populators */
|
||||||
private static final Handle POPULATE_ARRAY_BOOTSTRAP = new Handle(H_INVOKESTATIC, RewriteException.BOOTSTRAP.className(), RewriteException.BOOTSTRAP.name(), RewriteException.BOOTSTRAP.descriptor());
|
private static final Handle POPULATE_ARRAY_BOOTSTRAP = new Handle(H_INVOKESTATIC, RewriteException.BOOTSTRAP.className(), RewriteException.BOOTSTRAP.name(), RewriteException.BOOTSTRAP.descriptor());
|
||||||
|
|
||||||
|
@ -2187,25 +2183,6 @@ public class MethodEmitter implements Emitter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Generate dynamic getter. Pop scope from stack. Push result
|
||||||
*
|
*
|
||||||
|
|
|
@ -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<String, MethodHandle> METHODS;
|
|
||||||
|
|
||||||
/** Generic method cache */
|
|
||||||
private static final Map<String, MethodHandle> GENERIC_METHODS;
|
|
||||||
|
|
||||||
/** Unbox cache */
|
|
||||||
private static final Map<Class<?>, 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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -98,7 +98,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
* @return true if the node is a comparison operation.
|
||||||
*/
|
*/
|
||||||
public boolean isComparison() {
|
public boolean isComparison() {
|
||||||
|
@ -117,6 +117,22 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Returns true if the node is a logical operation.
|
||||||
* @return true if the node is a logical operation.
|
* @return true if the node is a logical operation.
|
||||||
|
|
|
@ -25,8 +25,6 @@
|
||||||
|
|
||||||
package jdk.nashorn.internal.ir;
|
package jdk.nashorn.internal.ir;
|
||||||
|
|
||||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -39,7 +37,7 @@ import jdk.nashorn.internal.parser.TokenType;
|
||||||
* IR representation for a runtime call.
|
* IR representation for a runtime call.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
public class RuntimeNode extends Expression implements Optimistic {
|
public class RuntimeNode extends Expression {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -333,11 +331,6 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||||
/** Call arguments. */
|
/** Call arguments. */
|
||||||
private final List<Expression> args;
|
private final List<Expression> args;
|
||||||
|
|
||||||
/** is final - i.e. may not be removed again, lower in the code pipeline */
|
|
||||||
private final boolean isFinal;
|
|
||||||
|
|
||||||
private final int programPoint;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
|
@ -351,17 +344,13 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||||
|
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.isFinal = false;
|
|
||||||
this.programPoint = INVALID_PROGRAM_POINT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final boolean isFinal, final List<Expression> args, final int programPoint) {
|
private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final List<Expression> args) {
|
||||||
super(runtimeNode);
|
super(runtimeNode);
|
||||||
|
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.isFinal = isFinal;
|
|
||||||
this.programPoint = programPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -399,8 +388,6 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||||
|
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.isFinal = false;
|
|
||||||
this.programPoint = parent instanceof Optimistic ? ((Optimistic)parent).getProgramPoint() : INVALID_PROGRAM_POINT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -431,28 +418,7 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||||
if (this.request == request) {
|
if (this.request == request) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new RuntimeNode(this, request, isFinal, args, programPoint);
|
return new RuntimeNode(this, request, args);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
return new RuntimeNode(this, request, isFinal, args, programPoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -510,7 +476,7 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||||
if (this.args == args) {
|
if (this.args == args) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new RuntimeNode(this, request, isFinal, args, programPoint);
|
return new RuntimeNode(this, request, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -536,39 +502,4 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -761,6 +761,36 @@ public enum JSType {
|
||||||
return toNumberGeneric(obj);
|
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
|
* JavaScript compliant conversion of Boolean to number
|
||||||
|
|
|
@ -536,8 +536,6 @@ public final class ScriptRuntime {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ECMA 11.6.1 - The addition operator (+) - generic implementation
|
* 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 x first term
|
||||||
* @param y second term
|
* @param y second term
|
||||||
|
@ -953,8 +951,15 @@ public final class ScriptRuntime {
|
||||||
* @return true if x is less than y
|
* @return true if x is less than y
|
||||||
*/
|
*/
|
||||||
public static boolean LT(final Object x, final Object y) {
|
public static boolean LT(final Object x, final Object y) {
|
||||||
final Object value = lessThan(x, y, true);
|
final Object px = JSType.toPrimitive(x, Number.class);
|
||||||
return value == UNDEFINED ? false : (Boolean)value;
|
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 @@ public final class ScriptRuntime {
|
||||||
* @return true if x is greater than y
|
* @return true if x is greater than y
|
||||||
*/
|
*/
|
||||||
public static boolean GT(final Object x, final Object y) {
|
public static boolean GT(final Object x, final Object y) {
|
||||||
final Object value = lessThan(y, x, false);
|
final Object px = JSType.toPrimitive(x, Number.class);
|
||||||
return value == UNDEFINED ? false : (Boolean)value;
|
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 @@ public final class ScriptRuntime {
|
||||||
* @return true if x is less than or equal to y
|
* @return true if x is less than or equal to y
|
||||||
*/
|
*/
|
||||||
public static boolean LE(final Object x, final Object y) {
|
public static boolean LE(final Object x, final Object y) {
|
||||||
final Object value = lessThan(y, x, false);
|
final Object px = JSType.toPrimitive(x, Number.class);
|
||||||
return !(Boolean.TRUE.equals(value) || value == UNDEFINED);
|
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 @@ public final class ScriptRuntime {
|
||||||
* @return true if x is greater than or equal to y
|
* @return true if x is greater than or equal to y
|
||||||
*/
|
*/
|
||||||
public static boolean GE(final Object x, final Object y) {
|
public static boolean GE(final Object x, final Object y) {
|
||||||
final Object value = lessThan(x, y, true);
|
final Object px = JSType.toPrimitive(x, Number.class);
|
||||||
return !(Boolean.TRUE.equals(value) || value == UNDEFINED);
|
final Object py = JSType.toPrimitive(y, Number.class);
|
||||||
}
|
|
||||||
|
|
||||||
/** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */
|
return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 :
|
||||||
private static Object lessThan(final Object x, final Object y, final boolean leftFirst) {
|
JSType.toNumber(px) >= JSType.toNumber(py);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1046,9 +1020,7 @@ public final class ScriptRuntime {
|
||||||
final Context context = Context.getContextTrusted();
|
final Context context = Context.getContextTrusted();
|
||||||
final SwitchPoint sp = context.getBuiltinSwitchPoint(name);
|
final SwitchPoint sp = context.getBuiltinSwitchPoint(name);
|
||||||
assert sp != null;
|
assert sp != null;
|
||||||
if (sp != null) {
|
|
||||||
context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint");
|
context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint");
|
||||||
SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
|
SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ import jdk.internal.dynalink.support.TypeUtilities;
|
||||||
import jdk.nashorn.api.scripting.JSObject;
|
import jdk.nashorn.api.scripting.JSObject;
|
||||||
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
||||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||||
import jdk.nashorn.internal.codegen.RuntimeCallSite;
|
|
||||||
import jdk.nashorn.internal.lookup.MethodHandleFactory;
|
import jdk.nashorn.internal.lookup.MethodHandleFactory;
|
||||||
import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
|
import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
|
||||||
import jdk.nashorn.internal.objects.ScriptFunctionImpl;
|
import jdk.nashorn.internal.objects.ScriptFunctionImpl;
|
||||||
|
@ -209,19 +208,6 @@ public final class Bootstrap {
|
||||||
return dynamicLinker.link(LinkerCallSite.newLinkerCallSite(lookup, opDesc, type, flags));
|
return dynamicLinker.link(LinkerCallSite.newLinkerCallSite(lookup, opDesc, type, flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Boostrapper for math calls that may overflow
|
||||||
* @param lookup lookup
|
* @param lookup lookup
|
||||||
|
|
282
nashorn/test/script/basic/JDK-8035712.js
Normal file
282
nashorn/test/script/basic/JDK-8035712.js
Normal file
|
@ -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]);
|
||||||
|
}
|
||||||
|
})();
|
Loading…
Add table
Add a link
Reference in a new issue