Bug 990106 part 4 - Recover Add and DCE unused additions. r=h4writer,jandem
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Tue, 29 Apr 2014 10:17:52 -0700
changeset 181119 8a3e7ed0c4c1c56bfe8547915e3a05a0c651d080
parent 181118 7977e7f8a0948976f4fbf3b4bdce74cff52dca1e
child 181120 6b76df1986a63b56d6cc1bfaa16a0b717e1e9507
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersh4writer, jandem
bugs990106
milestone32.0a1
Bug 990106 part 4 - Recover Add and DCE unused additions. r=h4writer,jandem
js/src/jit-test/tests/ion/dce-with-rinstructions.js
js/src/jit/IonAnalysis.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/Recover.cpp
js/src/jit/Recover.h
js/src/jsmath.cpp
js/src/jsmath.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -0,0 +1,72 @@
+setJitCompilerOption("baseline.usecount.trigger", 10);
+setJitCompilerOption("ion.usecount.trigger", 20);
+var i;
+
+// Check that we are able to remove the addition inside "ra" functions, when we
+// inline the first version of uceFault, and ensure that the bailout is correct
+// when uceFault is replaced (which cause an invalidation bailout)
+
+var uceFault = function (i) {
+    if (i > 98)
+        uceFault = function (i) { return true; };
+    return false;
+}
+
+var uceFault_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_number'));
+function ra_number(i) {
+    var x = 1 + i;
+    if (uceFault_number(i) || uceFault_number(i))
+        assertEq(x, 100  /* = 1 + 99 */);
+    return i;
+}
+
+var uceFault_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_float'));
+function ra_float(i) {
+    var t = Math.fround(1/3);
+    var fi = Math.fround(i);
+    var x = Math.fround(Math.fround(Math.fround(Math.fround(t + fi) + t) + fi) + t);
+    if (uceFault_float(i) || uceFault_float(i))
+        assertEq(x, 199); /* != 199.00000002980232 (when computed with double additions) */
+    return i;
+}
+
+var uceFault_string = eval(uneval(uceFault).replace('uceFault', 'uceFault_string'));
+function ra_string(i) {
+    var x = "s" + i;
+    if (uceFault_string(i) || uceFault_string(i))
+        assertEq(x, "s99");
+    return i;
+}
+
+var uceFault_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_object'));
+function ra_object(i) {
+    var x = {} + i;
+    if (uceFault_object(i) || uceFault_object(i))
+        assertEq(x, "[object Object]99");
+    return i;
+}
+
+for (i = 0; i < 100; i++) {
+    ra_number(i);
+    ra_float(i);
+    ra_string(i);
+    ra_object(i);
+}
+
+// Test that we can refer multiple time to the same recover instruction, as well
+// as chaining recover instructions.
+
+function alignedAlloc($size, $alignment) {
+    var $1 = $size + 4 | 0;
+    var $2 = $alignment - 1 | 0;
+    var $3 = $1 + $2 | 0;
+    var $4 = malloc($3);
+}
+
+function malloc($bytes) {
+    var $189 = undefined;
+    var $198 = $189 + 8 | 0;
+}
+
+for (i = 0; i < 50; i++)
+    alignedAlloc(608, 16);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -166,16 +166,19 @@ jit::EliminateDeadCode(MIRGenerator *mir
             return false;
 
         // Remove unused instructions.
         for (MInstructionReverseIterator inst = block->rbegin(); inst != block->rend(); ) {
             if (!inst->isEffectful() && !inst->resumePoint() &&
                 !inst->hasUses() && !inst->isGuard() &&
                 !inst->isControlInstruction()) {
                 inst = block->discardAt(inst);
+            } else if (!inst->hasLiveDefUses() && inst->canRecoverOnBailout()) {
+                inst->setRecoveredOnBailout();
+                inst++;
             } else {
                 inst++;
             }
         }
     }
 
     return true;
 }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -354,16 +354,30 @@ MDefinition::hasDefUses() const
     for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) {
         if ((*i)->consumer()->isDefinition())
             return true;
     }
 
     return false;
 }
 
+bool
+MDefinition::hasLiveDefUses() const
+{
+    for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) {
+        MNode *ins = (*i)->consumer();
+        if (!ins->isDefinition())
+            continue;
+        if (!ins->toDefinition()->isRecoveredOnBailout())
+            return true;
+    }
+
+    return false;
+}
+
 MUseIterator
 MDefinition::removeUse(MUseIterator use)
 {
     return uses_.removeAt(use);
 }
 
 MUseIterator
 MNode::replaceOperand(MUseIterator use, MDefinition *def)
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -535,16 +535,20 @@ class MDefinition : public MNode
     // Test whether this MDefinition has exactly one use.
     // (only counting MDefinitions, ignoring MResumePoints)
     bool hasOneDefUse() const;
 
     // Test whether this MDefinition has at least one use.
     // (only counting MDefinitions, ignoring MResumePoints)
     bool hasDefUses() const;
 
+    // Test whether this MDefinition has at least one non-recovered use.
+    // (only counting MDefinitions, ignoring MResumePoints)
+    bool hasLiveDefUses() const;
+
     bool hasUses() const {
         return !uses_.empty();
     }
 
     virtual bool isControlInstruction() const {
         return false;
     }
 
@@ -610,16 +614,20 @@ class MDefinition : public MNode
     virtual bool mightAlias(const MDefinition *store) const {
         // Return whether this load may depend on the specified store, given
         // that the alias sets intersect. This may be refined to exclude
         // possible aliasing in cases where alias set flags are too imprecise.
         JS_ASSERT(!isEffectful() && store->isEffectful());
         JS_ASSERT(getAliasSet().flags() & store->getAliasSet().flags());
         return true;
     }
+
+    virtual bool canRecoverOnBailout() const {
+        return false;
+    }
 };
 
 // An MUseDefIterator walks over uses in a definition, skipping any use that is
 // not a definition. Items from the use list must not be deleted during
 // iteration.
 class MUseDefIterator
 {
     MDefinition *def_;
@@ -4004,16 +4012,21 @@ class MAdd : public MBinaryArithInstruct
     double getIdentity() {
         return 0;
     }
 
     bool fallible() const;
     void computeRange(TempAllocator &alloc);
     bool truncate();
     bool isOperandTruncated(size_t index) const;
+
+    bool writeRecoverData(CompactBufferWriter &writer) const;
+    bool canRecoverOnBailout() const {
+        return specialization_ < MIRType_Object;
+    }
 };
 
 class MSub : public MBinaryArithInstruction
 {
     MSub(MDefinition *left, MDefinition *right)
       : MBinaryArithInstruction(left, right)
     {
         setResultType(MIRType_Value);
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -2,22 +2,25 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/Recover.h"
 
 #include "jscntxt.h"
+#include "jsmath.h"
 
 #include "jit/IonSpewer.h"
 #include "jit/JitFrameIterator.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 
+#include "vm/Interpreter.h"
+
 using namespace js;
 using namespace js::jit;
 
 bool
 MNode::writeRecoverData(CompactBufferWriter &writer) const
 {
     MOZ_ASSUME_UNREACHABLE("This instruction is not serializable");
     return false;
@@ -125,8 +128,42 @@ RResumePoint::RResumePoint(CompactBuffer
             pcOffset_, numOperands_);
 }
 
 bool
 RResumePoint::recover(JSContext *cx, SnapshotIterator &iter) const
 {
     MOZ_ASSUME_UNREACHABLE("This instruction is not recoverable.");
 }
+
+bool
+MAdd::writeRecoverData(CompactBufferWriter &writer) const
+{
+    MOZ_ASSERT(canRecoverOnBailout());
+    writer.writeUnsigned(uint32_t(RInstruction::Recover_Add));
+    writer.writeByte(specialization_ == MIRType_Float32);
+    return true;
+}
+
+RAdd::RAdd(CompactBufferReader &reader)
+{
+    isFloatOperation_ = reader.readByte();
+}
+
+bool
+RAdd::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    RootedValue lhs(cx, iter.read());
+    RootedValue rhs(cx, iter.read());
+    RootedValue result(cx);
+
+    MOZ_ASSERT(!lhs.isObject() && !rhs.isObject());
+    if (!js::AddValues(cx, &lhs, &rhs, &result))
+        return false;
+
+    // MIRType_Float32 is a specialization embedding the fact that the result is
+    // rounded to a Float32.
+    if (isFloatOperation_ && !RoundFloat32(cx, result, &result))
+        return false;
+
+    iter.storeInstructionResult(result);
+    return true;
+}
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -12,17 +12,18 @@
 #include "jit/Snapshots.h"
 
 class JSContext;
 
 namespace js {
 namespace jit {
 
 #define RECOVER_OPCODE_LIST(_)                  \
-    _(ResumePoint)
+    _(ResumePoint)                              \
+    _(Add)
 
 class RResumePoint;
 class SnapshotIterator;
 
 class RInstruction
 {
   public:
     enum Opcode
@@ -80,16 +81,31 @@ class RResumePoint MOZ_FINAL : public RI
         return pcOffset_;
     }
     virtual uint32_t numOperands() const {
         return numOperands_;
     }
     bool recover(JSContext *cx, SnapshotIterator &iter) const;
 };
 
+class RAdd MOZ_FINAL : public RInstruction
+{
+  private:
+    bool isFloatOperation_;
+
+  public:
+    RINSTRUCTION_HEADER_(Add)
+
+    virtual uint32_t numOperands() const {
+        return 2;
+    }
+
+    bool recover(JSContext *cx, SnapshotIterator &iter) const;
+};
+
 #undef RINSTRUCTION_HEADER_
 
 const RResumePoint *
 RInstruction::toResumePoint() const
 {
     MOZ_ASSERT(isResumePoint());
     return static_cast<const RResumePoint *>(this);
 }
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -458,25 +458,36 @@ js::math_imul(JSContext *cx, unsigned ar
     args.rval().setInt32(product > INT32_MAX
                          ? int32_t(INT32_MIN + (product - INT32_MAX - 1))
                          : int32_t(product));
     return true;
 }
 
 // Implements Math.fround (20.2.2.16) up to step 3
 bool
-js::RoundFloat32(JSContext *cx, Handle<Value> v, float *out)
+js::RoundFloat32(JSContext *cx, HandleValue v, float *out)
 {
     double d;
     bool success = ToNumber(cx, v, &d);
     *out = static_cast<float>(d);
     return success;
 }
 
 bool
+js::RoundFloat32(JSContext *cx, HandleValue arg, MutableHandleValue res)
+{
+    float f;
+    if (!RoundFloat32(cx, arg, &f))
+        return false;
+
+    res.setDouble(static_cast<double>(f));
+    return true;
+}
+
+bool
 js::math_fround(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() == 0) {
         args.rval().setNaN();
         return true;
     }
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -105,17 +105,20 @@ extern bool
 js_math_pow(JSContext *cx, unsigned argc, js::Value *vp);
 
 namespace js {
 
 extern bool
 math_imul(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
-RoundFloat32(JSContext *cx, Handle<Value> v, float *out);
+RoundFloat32(JSContext *cx, HandleValue v, float *out);
+
+extern bool
+RoundFloat32(JSContext *cx, HandleValue arg, MutableHandleValue res);
 
 extern bool
 math_fround(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
 math_log(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern double