8027042: Evaluation order for binary operators can be improved
Reviewed-by: lagergren, jlaskey, attila
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Fri Oct 25 10:20:49 2013 +0200
@@ -359,8 +359,11 @@
return load(node, node.hasType() ? node.getType() : null, false);
}
- private static boolean safeLiteral(final Expression rhs) {
- return rhs instanceof LiteralNode && !(rhs instanceof ArrayLiteralNode);
+ // Test whether conversion from source to target involves a call of ES 9.1 ToPrimitive
+ // with possible side effects from calling an object's toString or valueOf methods.
+ private boolean noToPrimitiveConversion(final Type source, final Type target) {
+ // Object to boolean conversion does not cause ToPrimitive call
+ return source.isJSPrimitive() || !target.isJSPrimitive() || target.isBoolean();
}
MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type) {
@@ -374,25 +377,19 @@
// can combine a LOAD with a CONVERT operation (e.g. use a dynamic getter with the conversion target type as its
// return value). What we do here is reorder LOAD RIGHT and CONVERT LEFT when possible; it is possible only when
// we can prove that executing CONVERT LEFT can't have a side effect that changes the value of LOAD RIGHT.
- // Basically, if we know that either LEFT is not an object, or RIGHT is a constant literal, then we can do the
+ // Basically, if we know that either LEFT already is a primitive value, or does not have to be converted to
+ // a primitive value, or RIGHT is an expression that loads without side effects, then we can do the
// reordering and collapse LOAD/CONVERT into a single operation; otherwise we need to do the more costly
// separate operations to preserve specification semantics.
- final Type lhsType = lhs.getType();
- if (lhsType.isObject() && !safeLiteral(rhs)) {
- // Can't reorder. Load and convert separately.
- load(lhs, lhsType, baseAlreadyOnStack);
- load(rhs, rhs.getType(), false);
- // Avoid empty SWAP, SWAP bytecode sequence if CONVERT LEFT is a no-op
- if (!lhsType.isEquivalentTo(type)) {
- method.swap();
- method.convert(type);
- method.swap();
- }
- method.convert(type);
- } else {
+ if (noToPrimitiveConversion(lhs.getType(), type) || rhs.isLocal()) {
// Can reorder. Combine load and convert into single operations.
load(lhs, type, baseAlreadyOnStack);
load(rhs, type, false);
+ } else {
+ // Can't reorder. Load and convert separately.
+ load(lhs, lhs.getType(), baseAlreadyOnStack);
+ load(rhs, rhs.getType(), false);
+ method.swap().convert(type).swap().convert(type);
}
return method;
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Fri Oct 25 10:20:49 2013 +0200
@@ -292,6 +292,16 @@
}
/**
+ * Determines whether this type represents an primitive type according to the ECMAScript specification,
+ * which includes Boolean, Number, and String.
+ *
+ * @return true if a JavaScript primitive type, false otherwise.
+ */
+ public boolean isJSPrimitive() {
+ return !isObject() || isString();
+ }
+
+ /**
* Determines whether a type is the BOOLEAN type
* @return true if BOOLEAN, false otherwise
*/
@@ -443,7 +453,7 @@
} else if (type0.isArray() != type1.isArray()) {
//array and non array is always object, widest(Object[], int) NEVER returns Object[], which has most weight. that does not make sense
return Type.OBJECT;
- } else if (type0.isObject() && type1.isObject() && ((ObjectType)type0).getTypeClass() != ((ObjectType)type1).getTypeClass()) {
+ } else if (type0.isObject() && type1.isObject() && type0.getTypeClass() != type1.getTypeClass()) {
// Object<type=String> and Object<type=ScriptFunction> will produce Object
// TODO: maybe find most specific common superclass?
return Type.OBJECT;
--- a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java Fri Oct 25 10:20:49 2013 +0200
@@ -90,6 +90,9 @@
return Type.LONG;
case ASSIGN_SAR:
case ASSIGN_SHL:
+ case BIT_AND:
+ case BIT_OR:
+ case BIT_XOR:
case ASSIGN_BIT_AND:
case ASSIGN_BIT_OR:
case ASSIGN_BIT_XOR:
@@ -170,6 +173,42 @@
}
@Override
+ public boolean isLocal() {
+ switch (tokenType()) {
+ case SAR:
+ case SHL:
+ case SHR:
+ case BIT_AND:
+ case BIT_OR:
+ case BIT_XOR:
+ case ADD:
+ case DIV:
+ case MOD:
+ case MUL:
+ case SUB:
+ return lhs.isLocal() && lhs.getType().isJSPrimitive()
+ && rhs.isLocal() && rhs.getType().isJSPrimitive();
+ case ASSIGN_ADD:
+ case ASSIGN_BIT_AND:
+ case ASSIGN_BIT_OR:
+ case ASSIGN_BIT_XOR:
+ case ASSIGN_DIV:
+ case ASSIGN_MOD:
+ case ASSIGN_MUL:
+ case ASSIGN_SAR:
+ case ASSIGN_SHL:
+ case ASSIGN_SHR:
+ case ASSIGN_SUB:
+ return lhs instanceof IdentNode && lhs.isLocal() && lhs.getType().isJSPrimitive()
+ && rhs.isLocal() && rhs.getType().isJSPrimitive();
+ case ASSIGN:
+ return lhs instanceof IdentNode && lhs.isLocal() && rhs.isLocal();
+ default:
+ return false;
+ }
+ }
+
+ @Override
public void toString(final StringBuilder sb) {
final TokenType type = tokenType();
--- a/nashorn/src/jdk/nashorn/internal/ir/Expression.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/Expression.java Fri Oct 25 10:20:49 2013 +0200
@@ -96,4 +96,16 @@
assert hasType() : this + " has no type";
return symbol.getSymbolType();
}
+
+ /**
+ * Returns {@code true} if this expression depends exclusively on state that is constant
+ * or local to the currently running function and thus inaccessible to other functions.
+ * This implies that a local expression must not call any other functions (neither directly
+ * nor implicitly through a getter, setter, or object-to-primitive type conversion).
+ *
+ * @return true if this expression does not depend on state shared with other functions.
+ */
+ public boolean isLocal() {
+ return false;
+ }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java Fri Oct 25 10:20:49 2013 +0200
@@ -138,6 +138,11 @@
return getName();
}
+ @Override
+ public boolean isLocal() {
+ return !getSymbol().isScope();
+ }
+
/**
* Check if this IdentNode is a property name
* @return true if this is a property name
--- a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java Fri Oct 25 10:20:49 2013 +0200
@@ -275,6 +275,11 @@
public boolean isTrue() {
return JSType.toBoolean(value);
}
+
+ @Override
+ public boolean isLocal() {
+ return true;
+ }
}
@Immutable
--- a/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java Fri Oct 25 10:20:49 2013 +0200
@@ -109,6 +109,13 @@
}
}
+ @Override
+ public boolean isLocal() {
+ return getTest().isLocal()
+ && getTrueExpression().isLocal()
+ && getFalseExpression().isLocal();
+ }
+
/**
* Get the test expression for this ternary expression, i.e. "x" in x ? y : z
* @return the test expression
--- a/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java Wed Oct 23 20:15:43 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java Fri Oct 25 10:20:49 2013 +0200
@@ -129,6 +129,26 @@
}
@Override
+ public boolean isLocal() {
+ switch (tokenType()) {
+ case NEW:
+ return false;
+ case ADD:
+ case SUB:
+ case NOT:
+ case BIT_NOT:
+ return rhs.isLocal() && rhs.getType().isJSPrimitive();
+ case DECPOSTFIX:
+ case DECPREFIX:
+ case INCPOSTFIX:
+ case INCPREFIX:
+ return rhs instanceof IdentNode && rhs.isLocal() && rhs.getType().isJSPrimitive();
+ default:
+ return rhs.isLocal();
+ }
+ }
+
+ @Override
public void toString(final StringBuilder sb) {
toString(sb, new Runnable() {
@Override
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8027042.js Fri Oct 25 10:20:49 2013 +0200
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8027042: Evaluation order for binary operators can be improved
+ *
+ * @test
+ * @run
+ */
+
+// var with getter side effect
+Object.defineProperty(this, "a", { get: function() {print("get a"); return 1; }});
+
+// var with both getter and conversion side effect
+Object.defineProperty(this, "b", { get: function() {print("get b"); return {valueOf: function() { print("conv b"); return 10; }}; }});
+
+(function() {
+ // var with toPrimitive conversion side effect
+ var c = {valueOf: function() { print("conv c"); return 100; }};
+
+ print(b + (c + a));
+ print(b + (c + b));
+ print(b + (a + b));
+ print(b + (b + c));
+ print(b + (b + c));
+ print(b + (c + (a - b)));
+ print(b + (c + (c - b)));
+ print(b + (c + (b - c)));
+ print(b + (b + (a ? 2 : 3)));
+ print(b + (b + (b ? 2 : 3)));
+ print(b + (b + (c ? 2 : 3)));
+ print(b + ((-c) + (-a)));
+ print(b + ((-c) + (-b)));
+ print(b + ((-c) + (-c)));
+ try { print(b + new a); } catch (e) {}
+ try { print(b + new b); } catch (e) {}
+ try { print(b + new c); } catch (e) {}
+})();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8027042.js.EXPECTED Fri Oct 25 10:20:49 2013 +0200
@@ -0,0 +1,88 @@
+get b
+get a
+conv c
+conv b
+111
+get b
+get b
+conv c
+conv b
+conv b
+120
+get b
+get a
+get b
+conv b
+conv b
+21
+get b
+get b
+conv b
+conv c
+conv b
+120
+get b
+get b
+conv b
+conv c
+conv b
+120
+get b
+get a
+get b
+conv b
+conv c
+conv b
+101
+get b
+get b
+conv c
+conv b
+conv c
+conv b
+200
+get b
+get b
+conv b
+conv c
+conv c
+conv b
+20
+get b
+get b
+get a
+conv b
+conv b
+22
+get b
+get b
+get b
+conv b
+conv b
+22
+get b
+get b
+conv b
+conv b
+22
+get b
+conv c
+get a
+conv b
+-91
+get b
+conv c
+get b
+conv b
+conv b
+-100
+get b
+conv c
+conv c
+conv b
+-190
+get b
+get a
+get b
+get b
+get b