Bug 1564942 - Part 2: Avoid negative zero check when the base operand in MPow is an Int32. r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Wed, 10 Jun 2020 11:51:58 +0000
changeset 598946 bfc836af8677902631e837dd59ca22f54cc151b2
parent 598945 5b9343de266b3a4b7fa585345027b6589ea4614f
child 598947 7765391a4142c32cc359872f4774249ab65950ee
push id13310
push userffxbld-merge
push dateMon, 29 Jun 2020 14:50:06 +0000
treeherdermozilla-beta@15a59a0afa5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1564942
milestone79.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 1564942 - Part 2: Avoid negative zero check when the base operand in MPow is an Int32. r=jandem That way the trailing DoubleToInt32 doesn't emit the negative zero check sequence: ``` movq %xmm0, %rax cmpq $0x1, %rax jo .Lfrom0000 ``` When MPow is used with a constant power which can be folded to MMul, this change will lead to better codegen, too. For example `Math.pow(x, 2)` where `x` is an Int32 value, currently generates the following assembly: ``` # instruction MoveGroup movl %eax, %ecx # instruction MulI:CanBeNegativeZero imull %ecx, %eax jo .Lfrom0000 testl %eax, %eax je .Lfrom0000 ``` With this patch, this assembly will be generated: ``` # instruction MoveGroup movl %eax, %ecx # instruction MulI imull %ecx, %eax jo .Lfrom0000 ``` Differential Revision: https://phabricator.services.mozilla.com/D37584
js/src/jit-test/tests/ion/pow-constant-power.js
js/src/jit/IonBuilder.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/pow-constant-power.js
@@ -0,0 +1,68 @@
+// Ion provides specialisations when Math.pow() resp. the **-operator is used
+// with a constant power of one of [-0.5, 0.5, 1, 2, 3, 4].
+
+function test(x, y, z) {
+    function pow(x, y) { return `Math.pow(${x}, ${y})` };
+    function exp(x, y) { return `((${x}) ** ${y})` };
+
+    function make(fn, x, y, z) {
+        return Function(`
+            // Load from array to prevent constant-folding.
+            // (Ion is currently not smart enough to realise that both array
+            // values are the same.)
+            var xs = [${x}, ${x}];
+            var zs = [${z}, ${z}];
+            for (var i = 0; i < 1000; ++i) {
+                assertEq(${fn("xs[i & 1]", y)}, zs[i & 1]);
+            }
+        `);
+    }
+
+    function double(v) {
+        // NB: Math.cbrt() always returns a double value.
+        return `Math.cbrt(${v * v * v})`;
+    }
+
+    function addTests(fn) {
+        tests.push(make(fn, x, y, z));
+        tests.push(make(fn, x, double(y), z));
+        tests.push(make(fn, double(x), y, z));
+        tests.push(make(fn, double(x), double(y), z));
+    }
+
+    var tests = [];
+    addTests(pow);
+    addTests(exp);
+
+    for (var i = 0; i < tests.length; ++i) {
+        for (var j = 0; j < 2; ++j) {
+            tests[i]();
+        }
+    }
+}
+
+// Make sure the tests below test int32 and double return values.
+
+// Math.pow(x, -0.5)
+test( 1, -0.5, 1);
+test(16, -0.5, 0.25);
+
+// Math.pow(x, 0.5)
+test(16, 0.5, 4);
+test( 2, 0.5, Math.SQRT2);
+
+// Math.pow(x, 1)
+test(5,   1, 5);
+test(0.5, 1, 0.5);
+
+// Math.pow(x, 2)
+test(5,   2, 25);
+test(0.5, 2, 0.25);
+
+// Math.pow(x, 3)
+test(5,   3, 125);
+test(0.5, 3, 0.125);
+
+// Math.pow(x, 3)
+test(5,   4, 625);
+test(0.5, 4, 0.0625);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -3607,16 +3607,17 @@ AbortReasonOr<Ok> IonBuilder::powTrySpec
 
   MPow* pow = MPow::New(alloc(), base, power, MIRType::Double);
   current->add(pow);
   output = pow;
 
   // Cast to the right type
   if (outputType == MIRType::Int32 && output->type() != MIRType::Int32) {
     auto* toInt = MToNumberInt32::New(alloc(), output);
+    toInt->setCanBeNegativeZero(pow->canBeNegativeZero());
     current->add(toInt);
     output = toInt;
   }
   if (outputType == MIRType::Double && output->type() != MIRType::Double) {
     MToDouble* toDouble = MToDouble::New(alloc(), output);
     current->add(toDouble);
     output = toDouble;
   }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2989,17 +2989,16 @@ MDefinition* MPow::foldsConstantPower(Te
   }
   if (!power()->toConstant()->isTypeRepresentableAsDouble()) {
     return nullptr;
   }
 
   MOZ_ASSERT(type() == MIRType::Double || type() == MIRType::Int32);
 
   double pow = power()->toConstant()->numberToDouble();
-  MIRType outputType = type();
 
   // Math.pow(x, 0.5) is a sqrt with edge-case detection.
   if (pow == 0.5) {
     MOZ_ASSERT(type() == MIRType::Double);
     return MPowHalf::New(alloc, input());
   }
 
   // Math.pow(x, -0.5) == 1 / Math.pow(x, 0.5), even for edge cases.
@@ -3012,33 +3011,41 @@ MDefinition* MPow::foldsConstantPower(Te
     return MDiv::New(alloc, one, half, MIRType::Double);
   }
 
   // Math.pow(x, 1) == x.
   if (pow == 1.0) {
     return input();
   }
 
+  auto multiply = [this, &alloc](MDefinition* lhs, MDefinition* rhs) {
+    MMul* mul = MMul::New(alloc, lhs, rhs, type());
+
+    // Multiplying the same number can't yield negative zero.
+    mul->setCanBeNegativeZero(lhs != rhs && canBeNegativeZero());
+    return mul;
+  };
+
   // Math.pow(x, 2) == x*x.
   if (pow == 2.0) {
-    return MMul::New(alloc, input(), input(), outputType);
+    return multiply(input(), input());
   }
 
   // Math.pow(x, 3) == x*x*x.
   if (pow == 3.0) {
-    MMul* mul1 = MMul::New(alloc, input(), input(), outputType);
+    MMul* mul1 = multiply(input(), input());
     block()->insertBefore(this, mul1);
-    return MMul::New(alloc, input(), mul1, outputType);
+    return multiply(input(), mul1);
   }
 
   // Math.pow(x, 4) == y*y, where y = x*x.
   if (pow == 4.0) {
-    MMul* y = MMul::New(alloc, input(), input(), outputType);
+    MMul* y = multiply(input(), input());
     block()->insertBefore(this, y);
-    return MMul::New(alloc, y, y, outputType);
+    return multiply(y, y);
   }
 
   // No optimization
   return nullptr;
 }
 
 MDefinition* MPow::foldsTo(TempAllocator& alloc) {
   if (MDefinition* def = foldsConstant(alloc)) {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -5169,29 +5169,47 @@ class MHypot : public MVariadicInstructi
 
   MInstruction* clone(TempAllocator& alloc,
                       const MDefinitionVector& inputs) const override {
     return MHypot::New(alloc, inputs);
   }
 };
 
 // Inline implementation of Math.pow().
+//
+// Supports the following three specializations:
+//
+// 1. MPow(FloatingPoint, FloatingPoint) -> Double
+//   - The most general mode, calls js::ecmaPow.
+//   - Never performs a bailout.
+// 2. MPow(FloatingPoint, Int32) -> Double
+//   - Optimization to call js::powi instead of js::ecmaPow.
+//   - Never performs a bailout.
+// 3. MPow(Int32, Int32) -> Int32
+//   - Performs the complete exponentiation operation in assembly code.
+//   - Bails out if the result doesn't fit in Int32.
 class MPow : public MBinaryInstruction, public PowPolicy::Data {
   // If true, convert the power operand to int32 instead of double (this only
   // affects the Double specialization). This exists because we can sometimes
   // get more precise types during MIR building than in type analysis.
-  bool powerIsInt32_;
+  bool powerIsInt32_ : 1;
+
+  // If true, the result is guaranteed to never be negative zero.
+  bool canBeNegativeZero_ : 1;
 
   MPow(MDefinition* input, MDefinition* power, MIRType specialization)
       : MBinaryInstruction(classOpcode, input, power),
         powerIsInt32_(power->type() == MIRType::Int32) {
     MOZ_ASSERT(specialization == MIRType::Int32 ||
                specialization == MIRType::Double);
     setResultType(specialization);
     setMovable();
+
+    // The result can't be negative zero if the base is an Int32 value.
+    canBeNegativeZero_ = input->type() != MIRType::Int32;
   }
 
   // Helpers for `foldsTo`
   MDefinition* foldsConstant(TempAllocator& alloc);
   MDefinition* foldsConstantPower(TempAllocator& alloc);
 
  public:
   INSTRUCTION_HEADER(Pow)
@@ -5207,16 +5225,17 @@ class MPow : public MBinaryInstruction, 
     }
     return congruentIfOperandsEqual(ins);
   }
   AliasSet getAliasSet() const override { return AliasSet::None(); }
   bool possiblyCalls() const override { return type() != MIRType::Int32; }
   MOZ_MUST_USE bool writeRecoverData(
       CompactBufferWriter& writer) const override;
   bool canRecoverOnBailout() const override { return true; }
+  bool canBeNegativeZero() const { return canBeNegativeZero_; }
 
   MDefinition* foldsTo(TempAllocator& alloc) override;
 
   ALLOW_CLONE(MPow)
 };
 
 // Inline implementation of Math.pow(x, 0.5), which subtly differs from
 // Math.sqrt(x).