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 486474 a23c3d37ae2b94c711059950b4f287e19c8f6b13
parent 486473 0d46368b8d77dbeedb4bf84f3a15b2e860a927ee
child 486475 131b2132cb79940808faf6b78a591ffc70df7a89
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1482359
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)