Bug 1482359: Use more JSOP_STRICTEQ optimizations for Object.is(). r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 13 Aug 2018 08:21:03 -0700
changeset 829005 a23c3d37ae2b94c711059950b4f287e19c8f6b13
parent 829004 0d46368b8d77dbeedb4bf84f3a15b2e860a927ee
child 829006 131b2132cb79940808faf6b78a591ffc70df7a89
push id118741
push userbmo:kshvmdn@gmail.com
push dateTue, 14 Aug 2018 18:31:47 +0000
reviewersjandem
bugs1482359
milestone63.0a1
Bug 1482359: Use more JSOP_STRICTEQ optimizations for Object.is(). r=jandem
js/src/jit-test/tests/ion/inlining/object-is-stricteq.js
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/MCallOptimize.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/inlining/object-is-stricteq.js
@@ -0,0 +1,143 @@
+// Test when Object.is() is inlined as JSOP_STRICTEQ
+
+function SameValue(x, y) {
+    if (x === y) {
+        return (x !== 0) || (1 / x === 1 / y);
+    }
+    return (x !== x && y !== y);
+}
+
+var compareTemplate = function compare(name, xs, ys) {
+    // Compare each entry in xs with each entry in ys and ensure Object.is
+    // computes the same result as SameValue.
+    for (var i = 0; i < 1000; ++i) {
+        var xi = (i % xs.length) | 0;
+        var yi = ((i + ((i / ys.length) | 0)) % ys.length) | 0;
+
+        assertEq(Object.is(xs[xi], ys[yi]), SameValue(xs[xi], ys[yi]), name);
+    }
+}
+
+const objects = {
+    plain: {},
+    function: function(){},
+    proxy: new Proxy({}, {}),
+};
+
+const sym = Symbol();
+
+const testCases = [
+    {
+        name: "Homogenous-Int32",
+        xs: [-1, 0, 1, 2, 0x7fffffff],
+        ys: [2, 1, 0, -5, -0x80000000],
+    },
+    {
+        name: "Homogenous-Boolean",
+        xs: [true, false],
+        ys: [true, false],
+    },
+    {
+        name: "Homogenous-Object",
+        xs: [{}, [], objects.plain, objects.proxy],
+        ys: [{}, objects.function, objects.plain, objects.proxy],
+    },
+    {
+        name: "Homogenous-String",
+        xs: ["", "abc", "αβγαβγ", "𝐀𝐁𝐂𝐀𝐁𝐂", "ABCabc"],
+        ys: ["abc", "ABC", "ABCABC", "αβγαβγ", "𝐀𝐁𝐂𝐀𝐁𝐂"],
+    },
+    {
+        name: "Homogenous-Symbol",
+        xs: [Symbol.iterator, Symbol(), Symbol.for("object-is"), sym],
+        ys: [sym, Symbol.match, Symbol(), Symbol.for("object-is-two")],
+    },
+    {
+        name: "Homogenous-Null",
+        xs: [null, null],
+        ys: [null, null],
+    },
+    {
+        name: "Homogenous-Undefined",
+        xs: [undefined, undefined],
+        ys: [undefined, undefined],
+    },
+
+    // Note: Values must not include floating-point types!
+    {
+        name: "String-Value",
+        xs: ["", "abc", "αβγαβγ", "𝐀𝐁𝐂𝐀𝐁𝐂"],
+        ys: [null, undefined, sym, true, 0, "", {}],
+    },
+    {
+        name: "Null-Value",
+        xs: [null, null],
+        ys: [null, undefined, sym, true, 0, "", {}],
+    },
+    {
+        name: "Undefined-Value",
+        xs: [undefined, undefined],
+        ys: [null, undefined, sym, true, 0, "", {}],
+    },
+    {
+        name: "Boolean-Value",
+        xs: [true, false],
+        ys: [null, undefined, sym, true, 0, "", {}],
+    },
+
+    // Note: Values must not include floating-point types!
+    {
+        name: "Value-String",
+        xs: [null, undefined, sym, true, 0, "", {}],
+        ys: ["", "abc", "αβγαβγ", "𝐀𝐁𝐂𝐀𝐁𝐂"],
+    },
+    {
+        name: "Value-Null",
+        xs: [null, undefined, sym, true, 0, "", {}],
+        ys: [null, null],
+    },
+    {
+        name: "Value-Undefined",
+        xs: [null, undefined, sym, true, 0, "", {}],
+        ys: [undefined, undefined],
+    },
+    {
+        name: "Value-Boolean",
+        xs: [null, undefined, sym, true, 0, "", {}],
+        ys: [undefined, undefined],
+    },
+
+    // Strict-equal comparison can be optimized to bitwise comparison when
+    // string types are not possible.
+    // Note: Values must not include floating-point types!
+    {
+        name: "Value-Value",
+        xs: [null, undefined, sym, true, 0, {}],
+        ys: [null, undefined, sym, true, 0, {}],
+    },
+    {
+        name: "ValueMaybeString-ValueMaybeString",
+        xs: [null, undefined, sym, true, 0, "", {}],
+        ys: [null, undefined, sym, true, 0, "", {}],
+    },
+    {
+        name: "Value-ValueMaybeString",
+        xs: [null, undefined, sym, true, 0, {}],
+        ys: [null, undefined, sym, true, 0, "", {}],
+    },
+    {
+        name: "ValueMaybeString-Value",
+        xs: [null, undefined, sym, true, 0, "", {}],
+        ys: [null, undefined, sym, true, 0, {}],
+    },
+];
+
+for (let {name, xs, ys} of testCases) {
+    // Create a separate function for each test case.
+    // Use indirect eval to avoid possible direct eval deopts.
+    const compare = (0, eval)(`(${compareTemplate})`);
+
+    for (let i = 0; i < 5; ++i) {
+        compare(name, xs, ys);
+    }
+}
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5810,67 +5810,79 @@ IonBuilder::jsop_compare(JSOp op)
     MDefinition* left = current->pop();
 
     return jsop_compare(op, left, right);
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_compare(JSOp op, MDefinition* left, MDefinition* right)
 {
+    // TODO: Support tracking optimizations for inlining a call and regular
+    // optimization tracking at the same time. Currently just drop optimization
+    // tracking when that happens.
+    bool canTrackOptimization = !IsCallPC(pc);
+
     bool emitted = false;
-    startTrackingOptimizations();
+    if (canTrackOptimization)
+        startTrackingOptimizations();
 
     if (!forceInlineCaches()) {
-        MOZ_TRY(compareTrySpecialized(&emitted, op, left, right, true));
+        MOZ_TRY(compareTrySpecialized(&emitted, op, left, right));
         if (emitted)
             return Ok();
         MOZ_TRY(compareTryBitwise(&emitted, op, left, right));
         if (emitted)
             return Ok();
         MOZ_TRY(compareTrySpecializedOnBaselineInspector(&emitted, op, left, right));
         if (emitted)
             return Ok();
     }
 
     MOZ_TRY(compareTryBinaryStub(&emitted, left, right));
     if (emitted)
         return Ok();
 
-    trackOptimizationAttempt(TrackedStrategy::Compare_Call);
+    if (canTrackOptimization)
+        trackOptimizationAttempt(TrackedStrategy::Compare_Call);
 
     // Not possible to optimize. Do a slow vm call.
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
     current->add(ins);
     current->push(ins);
     if (ins->isEffectful())
         MOZ_TRY(resumeAfter(ins));
 
-    trackOptimizationSuccess();
+    if (canTrackOptimization)
+        trackOptimizationSuccess();
     return Ok();
 }
 
 static bool
 ObjectOrSimplePrimitive(MDefinition* op)
 {
     // Return true if op is either undefined/null/boolean/int32/symbol or an object.
     return !op->mightBeType(MIRType::String)
         && !op->mightBeType(MIRType::Double)
         && !op->mightBeType(MIRType::Float32)
         && !op->mightBeType(MIRType::MagicOptimizedArguments)
         && !op->mightBeType(MIRType::MagicHole)
         && !op->mightBeType(MIRType::MagicIsConstructing);
 }
 
 AbortReasonOr<Ok>
-IonBuilder::compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right,
-                                  bool canTrackOptimization)
+IonBuilder::compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
+
+    // TODO: Support tracking optimizations for inlining a call and regular
+    // optimization tracking at the same time. Currently just drop optimization
+    // tracking when that happens.
+    bool canTrackOptimization = !IsCallPC(pc);
     if (canTrackOptimization)
         trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedTypes);
 
     // Try to emit an compare based on the input types.
 
     MCompare::CompareType type = MCompare::determineCompareType(op, left, right);
     if (type == MCompare::Compare_Unknown) {
         if (canTrackOptimization)
@@ -5906,98 +5918,116 @@ IonBuilder::compareTrySpecialized(bool* 
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::compareTryBitwise(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
-    trackOptimizationAttempt(TrackedStrategy::Compare_Bitwise);
+
+    // TODO: Support tracking optimizations for inlining a call and regular
+    // optimization tracking at the same time. Currently just drop optimization
+    // tracking when that happens.
+    bool canTrackOptimization = !IsCallPC(pc);
+    if (canTrackOptimization)
+        trackOptimizationAttempt(TrackedStrategy::Compare_Bitwise);
 
     // Try to emit a bitwise compare. Check if a bitwise compare equals the wanted
     // result for all observed operand types.
 
     // Only allow loose and strict equality.
     if (op != JSOP_EQ && op != JSOP_NE && op != JSOP_STRICTEQ && op != JSOP_STRICTNE) {
-        trackOptimizationOutcome(TrackedOutcome::RelationalCompare);
+        if (canTrackOptimization)
+            trackOptimizationOutcome(TrackedOutcome::RelationalCompare);
         return Ok();
     }
 
     // Only primitive (not double/string) or objects are supported.
     // I.e. Undefined/Null/Boolean/Int32/Symbol and Object
     if (!ObjectOrSimplePrimitive(left) || !ObjectOrSimplePrimitive(right)) {
-        trackOptimizationOutcome(TrackedOutcome::OperandTypeNotBitwiseComparable);
+        if (canTrackOptimization)
+            trackOptimizationOutcome(TrackedOutcome::OperandTypeNotBitwiseComparable);
         return Ok();
     }
 
     // Objects that emulate undefined are not supported.
     if (left->maybeEmulatesUndefined(constraints()) ||
         right->maybeEmulatesUndefined(constraints()))
     {
-        trackOptimizationOutcome(TrackedOutcome::OperandMaybeEmulatesUndefined);
+        if (canTrackOptimization)
+            trackOptimizationOutcome(TrackedOutcome::OperandMaybeEmulatesUndefined);
         return Ok();
     }
 
     // In the loose comparison more values could be the same,
     // but value comparison reporting otherwise.
     if (op == JSOP_EQ || op == JSOP_NE) {
 
         // Undefined compared loosy to Null is not supported,
         // because tag is different, but value can be the same (undefined == null).
         if ((left->mightBeType(MIRType::Undefined) && right->mightBeType(MIRType::Null)) ||
             (left->mightBeType(MIRType::Null) && right->mightBeType(MIRType::Undefined)))
         {
-            trackOptimizationOutcome(TrackedOutcome::LoosyUndefinedNullCompare);
+            if (canTrackOptimization)
+                trackOptimizationOutcome(TrackedOutcome::LoosyUndefinedNullCompare);
             return Ok();
         }
 
         // Int32 compared loosy to Boolean is not supported,
         // because tag is different, but value can be the same (1 == true).
         if ((left->mightBeType(MIRType::Int32) && right->mightBeType(MIRType::Boolean)) ||
             (left->mightBeType(MIRType::Boolean) && right->mightBeType(MIRType::Int32)))
         {
-            trackOptimizationOutcome(TrackedOutcome::LoosyInt32BooleanCompare);
+            if (canTrackOptimization)
+                trackOptimizationOutcome(TrackedOutcome::LoosyInt32BooleanCompare);
             return Ok();
         }
 
         // For loosy comparison of an object with a Boolean/Number/String/Symbol
         // the valueOf the object is taken. Therefore not supported.
         bool simpleLHS = left->mightBeType(MIRType::Boolean) ||
                          left->mightBeType(MIRType::Int32) ||
                          left->mightBeType(MIRType::Symbol);
         bool simpleRHS = right->mightBeType(MIRType::Boolean) ||
                          right->mightBeType(MIRType::Int32) ||
                          right->mightBeType(MIRType::Symbol);
         if ((left->mightBeType(MIRType::Object) && simpleRHS) ||
             (right->mightBeType(MIRType::Object) && simpleLHS))
         {
-            trackOptimizationOutcome(TrackedOutcome::CallsValueOf);
+            if (canTrackOptimization)
+                trackOptimizationOutcome(TrackedOutcome::CallsValueOf);
             return Ok();
         }
     }
 
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->setCompareType(MCompare::Compare_Bitwise);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
     current->add(ins);
     current->push(ins);
 
     MOZ_ASSERT(!ins->isEffectful());
-    trackOptimizationSuccess();
+    if (canTrackOptimization)
+        trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, MDefinition* left,
                                                      MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
+
+    // Not supported for call expressions.
+    if (IsCallPC(pc))
+        return Ok();
+
     trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedOnBaselineTypes);
 
     // Try to specialize based on any baseline caches that have been generated
     // for the opcode. These will cause the instruction's type policy to insert
     // fallible unboxes to the appropriate input types.
 
     // Strict equality isn't supported.
     if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) {
@@ -6029,17 +6059,17 @@ IonBuilder::compareTryBinaryStub(bool* e
 {
     MOZ_ASSERT(*emitted == false);
 
     // Try to emit a shared stub cache.
 
     if (JitOptions.disableSharedStubs)
         return Ok();
 
-    if (JSOp(*pc) == JSOP_CASE)
+    if (JSOp(*pc) == JSOP_CASE || IsCallPC(pc))
         return Ok();
 
     MBinaryCache* stub = MBinaryCache::New(alloc(), left, right);
     current->add(stub);
     current->push(stub);
     MOZ_TRY(resumeAfter(stub));
 
     MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Boolean, MUnbox::Infallible);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -321,17 +321,17 @@ class IonBuilder
     AbortReasonOr<Ok> bitnotTrySpecialized(bool* emitted, MDefinition* input);
 
     // jsop_pow helpers.
     AbortReasonOr<Ok> powTrySpecialized(bool* emitted, MDefinition* base, MDefinition* power,
                                         MIRType outputType);
 
     // jsop_compare helpers.
     AbortReasonOr<Ok> compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left,
-                                            MDefinition* right, bool canTrackOptimization);
+                                            MDefinition* right);
     AbortReasonOr<Ok> compareTryBitwise(bool* emitted, JSOp op, MDefinition* left,
                                         MDefinition* right);
     AbortReasonOr<Ok> compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op,
                                                                MDefinition* left,
                                                                MDefinition* right);
     AbortReasonOr<Ok> compareTryBinaryStub(bool* emitted, MDefinition* left, MDefinition* right);
 
     // jsop_newarray helpers.
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -2483,58 +2483,60 @@ IonBuilder::inlineObjectIs(CallInfo& cal
     if (getInlineReturnType() != MIRType::Boolean)
         return InliningStatus_NotInlined;
 
     MDefinition* left = callInfo.getArg(0);
     MDefinition* right = callInfo.getArg(1);
     MIRType leftType = left->type();
     MIRType rightType = right->type();
 
+    auto mightBeFloatingPointType = [](MDefinition* def) {
+        return def->mightBeType(MIRType::Double) || def->mightBeType(MIRType::Float32);
+    };
+
     bool strictEq;
     bool incompatibleTypes = false;
     if (leftType == rightType) {
         // We can only compare the arguments with strict-equals semantics if
-        // they aren't floating-point types or values. Otherwise we need to
-        // use MSameValue.
-        strictEq = !(IsFloatingPointType(leftType) || leftType == MIRType::Value);
+        // they aren't floating-point types or values which might be floating-
+        // point types. Otherwise we need to use MSameValue.
+        strictEq = leftType != MIRType::Value
+                   ? !IsFloatingPointType(leftType)
+                   : (!mightBeFloatingPointType(left) && !mightBeFloatingPointType(right));
     } else if (leftType == MIRType::Value) {
-        // Also use strict-equals when comparing a value with a non-number.
-        strictEq = !IsNumberType(rightType);
+        // Also use strict-equals when comparing a value with a non-number or
+        // the value cannot be a floating-point type.
+        strictEq = !IsNumberType(rightType) || !mightBeFloatingPointType(left);
     } else if (rightType == MIRType::Value) {
         // Dual case to the previous one, only with reversed operands.
-        strictEq = !IsNumberType(leftType);
+        strictEq = !IsNumberType(leftType) || !mightBeFloatingPointType(right);
     } else if (IsNumberType(leftType) && IsNumberType(rightType)) {
         // Both arguments are numbers, but with different representations. We
         // can't use strict-equals semantics to compare the operands, but
         // instead need to use MSameValue.
         strictEq = false;
     } else {
         incompatibleTypes = true;
     }
 
     if (incompatibleTypes) {
         // The result is always |false| when comparing incompatible types.
         pushConstant(BooleanValue(false));
+    } else if (strictEq) {
+        // Specialize |Object.is(lhs, rhs)| as |lhs === rhs|.
+        MOZ_TRY(jsop_compare(JSOP_STRICTEQ, left, right));
     } else {
-        bool emitted = false;
-        if (strictEq) {
-            // Specialize |Object.is(lhs, rhs)| as |lhs === rhs|.
-            MOZ_TRY(compareTrySpecialized(&emitted, JSOP_STRICTEQ, left, right, false));
-        }
-
-        if (!emitted) {
-            MSameValue* ins = MSameValue::New(alloc(), left, right);
-
-            // The more specific operand is expected to be in the rhs.
-            if (IsNumberType(leftType) && rightType == MIRType::Value)
-                ins->swapOperands();
-
-            current->add(ins);
-            current->push(ins);
-        }
+        MSameValue* ins = MSameValue::New(alloc(), left, right);
+
+        // The more specific operand is expected to be in the rhs.
+        if (IsNumberType(leftType) && rightType == MIRType::Value)
+            ins->swapOperands();
+
+        current->add(ins);
+        current->push(ins);
     }
 
     callInfo.setImplicitlyUsedUnchecked();
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningResult
 IonBuilder::inlineObjectToString(CallInfo& callInfo)