Bug 990106 part 3 - Recover RInstructions during bailouts. r=jandem
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Tue, 29 Apr 2014 10:17:51 -0700
changeset 180712 7977e7f8a0948976f4fbf3b4bdce74cff52dca1e
parent 180711 85b6c3b4b26da80194e63cdc2f1692c13b4b69b2
child 180713 8a3e7ed0c4c1c56bfe8547915e3a05a0c651d080
push id42865
push usernpierron@mozilla.com
push dateTue, 29 Apr 2014 17:18:26 +0000
treeherdermozilla-inbound@6b76df1986a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs990106
milestone32.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 990106 part 3 - Recover RInstructions during bailouts. r=jandem
js/public/Value.h
js/src/jit/Bailouts.cpp
js/src/jit/BaselineBailouts.cpp
js/src/jit/IonFrames.cpp
js/src/jit/JitFrameIterator.h
js/src/jit/Recover.cpp
js/src/jit/Recover.h
js/src/jit/Snapshots.cpp
js/src/jit/Snapshots.h
js/src/jit/shared/CodeGenerator-shared.cpp
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -231,17 +231,17 @@ typedef enum JSWhyMagic
     JS_SERIALIZE_NO_NODE,        /* an empty subnode in the AST serializer */
     JS_LAZY_ARGUMENTS,           /* lazy arguments value on the stack */
     JS_OPTIMIZED_ARGUMENTS,      /* optimized-away 'arguments' value */
     JS_IS_CONSTRUCTING,          /* magic value passed to natives to indicate construction */
     JS_OVERWRITTEN_CALLEE,       /* arguments.callee has been overwritten */
     JS_BLOCK_NEEDS_CLONE,        /* value of static block object slot */
     JS_HASH_KEY_EMPTY,           /* see class js::HashableValue */
     JS_ION_ERROR,                /* error while running Ion code */
-    JS_ION_BAILOUT,              /* status code to signal EnterIon will OSR into Interpret */
+    JS_ION_BAILOUT,              /* missing recover instruction result */
     JS_OPTIMIZED_OUT,            /* optimized out slot */
     JS_GENERIC_MAGIC             /* for local use */
 } JSWhyMagic;
 
 #if defined(IS_LITTLE_ENDIAN)
 # if JS_BITS_PER_WORD == 32
 typedef union jsval_layout
 {
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -42,17 +42,18 @@ SnapshotIterator::SnapshotIterator(const
               iter.snapshotOffset(),
               iter.ionScript()->snapshotsRVATableSize(),
               iter.ionScript()->snapshotsListSize()),
     recover_(snapshot_,
              iter.ionScript()->recovers(),
              iter.ionScript()->recoversSize()),
     fp_(iter.jsFrame()),
     machine_(iter.machineState()),
-    ionScript_(iter.ionScript())
+    ionScript_(iter.ionScript()),
+    instructionResults_(nullptr)
 {
 }
 
 void
 IonBailoutIterator::dump() const
 {
     if (type_ == JitFrame_IonJS) {
         InlineFrameIterator frames(GetJSContextFromJitCode(), this);
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1331,18 +1331,22 @@ jit::BailoutIonToBaseline(JSContext *cx,
     iter.script()->updateBaselineOrIonRaw();
 
     // Allocate buffer to hold stack replacement data.
     BaselineStackBuilder builder(iter, 1024);
     if (!builder.init())
         return BAILOUT_RETURN_FATAL_ERROR;
     IonSpew(IonSpew_BaselineBailouts, "  Incoming frame ptr = %p", builder.startFrame());
 
+    AutoValueVector instructionResults(cx);
     SnapshotIterator snapIter(iter);
 
+    if (!snapIter.initIntructionResults(instructionResults))
+        return BAILOUT_RETURN_FATAL_ERROR;
+
     RootedFunction callee(cx, iter.maybeCallee());
     RootedScript scr(cx, iter.script());
     if (callee) {
         IonSpew(IonSpew_BaselineBailouts, "  Callee function (%s:%u)",
                 scr->filename(), scr->lineno());
     } else {
         IonSpew(IonSpew_BaselineBailouts, "  No callee!");
     }
@@ -1360,17 +1364,22 @@ jit::BailoutIonToBaseline(JSContext *cx,
     jsbytecode *callerPC = nullptr;
     RootedFunction fun(cx, callee);
     AutoValueVector startFrameFormals(cx);
 
     RootedScript topCaller(cx);
     jsbytecode *topCallerPC = nullptr;
 
     while (true) {
-        MOZ_ASSERT(snapIter.instruction()->isResumePoint());
+        if (!snapIter.instruction()->isResumePoint()) {
+            if (!snapIter.instruction()->recover(cx, snapIter))
+                return BAILOUT_RETURN_FATAL_ERROR;
+            snapIter.nextInstruction();
+            continue;
+        }
 
         if (frameNo > 0) {
             TraceLogStartEvent(logger, TraceLogCreateTextId(logger, scr));
             TraceLogStartEvent(logger, TraceLogger::Baseline);
         }
 
         IonSpew(IonSpew_BaselineBailouts, "    FrameNo %d", frameNo);
 
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -1333,40 +1333,43 @@ SnapshotIterator::SnapshotIterator(IonSc
               snapshotOffset,
               ionScript->snapshotsRVATableSize(),
               ionScript->snapshotsListSize()),
     recover_(snapshot_,
              ionScript->recovers(),
              ionScript->recoversSize()),
     fp_(fp),
     machine_(machine),
-    ionScript_(ionScript)
+    ionScript_(ionScript),
+    instructionResults_(nullptr)
 {
     JS_ASSERT(snapshotOffset < ionScript->snapshotsListSize());
 }
 
 SnapshotIterator::SnapshotIterator(const JitFrameIterator &iter)
   : snapshot_(iter.ionScript()->snapshots(),
               iter.osiIndex()->snapshotOffset(),
               iter.ionScript()->snapshotsRVATableSize(),
               iter.ionScript()->snapshotsListSize()),
     recover_(snapshot_,
              iter.ionScript()->recovers(),
              iter.ionScript()->recoversSize()),
     fp_(iter.jsFrame()),
     machine_(iter.machineState()),
-    ionScript_(iter.ionScript())
+    ionScript_(iter.ionScript()),
+    instructionResults_(nullptr)
 {
 }
 
 SnapshotIterator::SnapshotIterator()
   : snapshot_(nullptr, 0, 0, 0),
     recover_(snapshot_, nullptr, 0),
     fp_(nullptr),
-    ionScript_(nullptr)
+    ionScript_(nullptr),
+    instructionResults_(nullptr)
 {
 }
 
 uintptr_t
 SnapshotIterator::fromStack(int32_t offset) const
 {
     return ReadFrameSlot(fp_, offset);
 }
@@ -1421,16 +1424,19 @@ SnapshotIterator::allocationReadable(con
         return hasStack(alloc.stackOffset()) && hasStack(alloc.stackOffset2());
 #elif defined(JS_PUNBOX64)
       case RValueAllocation::UNTYPED_REG:
         return hasRegister(alloc.reg());
       case RValueAllocation::UNTYPED_STACK:
         return hasStack(alloc.stackOffset());
 #endif
 
+      case RValueAllocation::RECOVER_INSTRUCTION:
+        return hasInstructionResult(alloc.index());
+
       default:
         return true;
     }
 }
 
 Value
 SnapshotIterator::allocationValue(const RValueAllocation &alloc)
 {
@@ -1526,31 +1532,34 @@ SnapshotIterator::allocationValue(const 
       case RValueAllocation::UNTYPED_STACK:
       {
         jsval_layout layout;
         layout.asBits = fromStack(alloc.stackOffset());
         return IMPL_TO_JSVAL(layout);
       }
 #endif
 
+      case RValueAllocation::RECOVER_INSTRUCTION:
+        return fromInstructionResult(alloc.index());
+
       default:
         MOZ_ASSUME_UNREACHABLE("huh?");
     }
 }
 
 const RResumePoint *
 SnapshotIterator::resumePoint() const
 {
     return instruction()->toResumePoint();
 }
 
 uint32_t
 SnapshotIterator::numAllocations() const
 {
-    return resumePoint()->numOperands();
+    return instruction()->numOperands();
 }
 
 uint32_t
 SnapshotIterator::pcOffset() const
 {
     return resumePoint()->pcOffset();
 }
 
@@ -1559,16 +1568,53 @@ SnapshotIterator::skipInstruction()
 {
     MOZ_ASSERT(snapshot_.numAllocationsRead() == 0);
     size_t numOperands = instruction()->numOperands();
     for (size_t i = 0; i < numOperands; i++)
         skip();
     nextInstruction();
 }
 
+bool
+SnapshotIterator::initIntructionResults(AutoValueVector &results)
+{
+    MOZ_ASSERT(recover_.numInstructionsRead() == 1);
+
+    // The last instruction will always be a resume point, no need to allocate
+    // space for it.
+    if (recover_.numInstructions() == 1)
+        return true;
+
+    MOZ_ASSERT(recover_.numInstructions() > 1);
+    size_t numResults = recover_.numInstructions() - 1;
+    if (!results.reserve(numResults))
+        return false;
+
+    for (size_t i = 0; i < numResults; i++)
+        results.infallibleAppend(MagicValue(JS_ION_BAILOUT));
+
+    instructionResults_ = &results;
+    return true;
+}
+
+void
+SnapshotIterator::storeInstructionResult(Value v)
+{
+    uint32_t currIns = recover_.numInstructionsRead() - 1;
+    MOZ_ASSERT((*instructionResults_)[currIns].isMagic(JS_ION_BAILOUT));
+    (*instructionResults_)[currIns].set(v);
+}
+
+Value
+SnapshotIterator::fromInstructionResult(uint32_t index) const
+{
+    MOZ_ASSERT(!(*instructionResults_)[index].isMagic(JS_ION_BAILOUT));
+    return (*instructionResults_)[index];
+}
+
 void
 SnapshotIterator::nextFrame()
 {
     nextInstruction();
     while (!instruction()->isResumePoint())
         skipInstruction();
 }
 
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -253,16 +253,17 @@ class RResumePoint;
 // to innermost frame).
 class SnapshotIterator
 {
     SnapshotReader snapshot_;
     RecoverReader recover_;
     IonJSFrameLayout *fp_;
     MachineState machine_;
     IonScript *ionScript_;
+    AutoValueVector *instructionResults_;
 
   private:
     // Read a spilled register from the machine state.
     bool hasRegister(Register reg) const {
         return machine_.has(reg);
     }
     uintptr_t fromRegister(Register reg) const {
         return machine_.read(reg);
@@ -276,16 +277,21 @@ class SnapshotIterator
     }
 
     // Read an uintptr_t from the stack.
     bool hasStack(int32_t offset) const {
         return true;
     }
     uintptr_t fromStack(int32_t offset) const;
 
+    bool hasInstructionResult(uint32_t index) const {
+        return instructionResults_;
+    }
+    Value fromInstructionResult(uint32_t index) const;
+
     Value allocationValue(const RValueAllocation &a);
     bool allocationReadable(const RValueAllocation &a);
     void warnUnreadableAllocation();
 
   public:
     // Handle iterating over RValueAllocations of the snapshots.
     inline RValueAllocation readAllocation() {
         MOZ_ASSERT(moreAllocations());
@@ -333,16 +339,24 @@ class SnapshotIterator
     // Skip an Instruction by walking to the next instruction and by skipping
     // all the allocations corresponding to this instruction.
     void skipInstruction();
 
     inline bool moreInstructions() const {
         return recover_.moreInstructions();
     }
 
+    // Register a vector used for storing the results of the evaluation of
+    // recover instructions. This vector should be registered before the
+    // beginning of the iteration. This function is in charge of allocating
+    // enough space for all instructions results, and return false iff it fails.
+    bool initIntructionResults(AutoValueVector &results);
+
+    void storeInstructionResult(Value v);
+
   public:
     // Handle iterating over frames of the snapshots.
     void nextFrame();
 
     inline bool moreFrames() const {
         // The last instruction is recovering the innermost frame, so as long as
         // there is more instruction there is necesseray more frames.
         return moreInstructions();
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1,17 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * 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 "jit/IonSpewer.h"
+#include "jit/JitFrameIterator.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 
 using namespace js;
 using namespace js::jit;
 
 bool
 MNode::writeRecoverData(CompactBufferWriter &writer) const
@@ -20,19 +23,27 @@ MNode::writeRecoverData(CompactBufferWri
     return false;
 }
 
 void
 RInstruction::readRecoverData(CompactBufferReader &reader, RInstructionStorage *raw)
 {
     uint32_t op = reader.readUnsigned();
     switch (Opcode(op)) {
-      case Recover_ResumePoint:
-        new (raw->addr()) RResumePoint(reader);
+#   define MATCH_OPCODES_(op)                                           \
+      case Recover_##op:                                                \
+        static_assert(sizeof(R##op) <= sizeof(RInstructionStorage),     \
+                      "Storage space is too small to decode R" #op " instructions."); \
+        new (raw->addr()) R##op(reader);                                \
         break;
+
+        RECOVER_OPCODE_LIST(MATCH_OPCODES_)
+#   undef DEFINE_OPCODES_
+
+      case Recover_Invalid:
       default:
         MOZ_ASSUME_UNREACHABLE("Bad decoding of the previous instruction?");
         break;
     }
 }
 
 bool
 MResumePoint::writeRecoverData(CompactBufferWriter &writer) const
@@ -103,15 +114,19 @@ MResumePoint::writeRecoverData(CompactBu
     IonSpew(IonSpew_Snapshots, "Writing pc offset %u, nslots %u", pcoff, nallocs);
     writer.writeUnsigned(pcoff);
     writer.writeUnsigned(nallocs);
     return true;
 }
 
 RResumePoint::RResumePoint(CompactBufferReader &reader)
 {
-    static_assert(sizeof(*this) <= sizeof(RInstructionStorage),
-                  "Storage space is too small to decode this recover instruction.");
     pcOffset_ = reader.readUnsigned();
     numOperands_ = reader.readUnsigned();
     IonSpew(IonSpew_Snapshots, "Read RResumePoint (pc offset %u, nslots %u)",
             pcOffset_, numOperands_);
 }
+
+bool
+RResumePoint::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    MOZ_ASSUME_UNREACHABLE("This instruction is not recoverable.");
+}
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -6,63 +6,92 @@
 
 #ifndef jit_Recover_h
 #define jit_Recover_h
 
 #include "mozilla/Attributes.h"
 
 #include "jit/Snapshots.h"
 
+class JSContext;
+
 namespace js {
 namespace jit {
 
+#define RECOVER_OPCODE_LIST(_)                  \
+    _(ResumePoint)
+
 class RResumePoint;
+class SnapshotIterator;
 
 class RInstruction
 {
   public:
     enum Opcode
     {
-        Recover_ResumePoint = 0
+#   define DEFINE_OPCODES_(op) Recover_##op,
+        RECOVER_OPCODE_LIST(DEFINE_OPCODES_)
+#   undef DEFINE_OPCODES_
+        Recover_Invalid
     };
 
     virtual Opcode opcode() const = 0;
 
+    // As opposed to the MIR, there is no need to add more methods as every
+    // other instruction is well abstracted under the "recover" method.
     bool isResumePoint() const {
         return opcode() == Recover_ResumePoint;
     }
     inline const RResumePoint *toResumePoint() const;
 
+    // Number of allocations which are encoded in the Snapshot for recovering
+    // the current instruction.
     virtual uint32_t numOperands() const = 0;
 
+    // Function used to recover the value computed by this instruction. This
+    // function reads its arguments from the allocations listed on the snapshot
+    // iterator and stores its returned value on the snapshot iterator too.
+    virtual bool recover(JSContext *cx, SnapshotIterator &iter) const = 0;
+
+    // Decode an RInstruction on top of the reserved storage space, based on the
+    // tag written by the writeRecoverData function of the corresponding MIR
+    // instruction.
     static void readRecoverData(CompactBufferReader &reader, RInstructionStorage *raw);
 };
 
+#define RINSTRUCTION_HEADER_(op)                                        \
+  private:                                                              \
+    friend class RInstruction;                                          \
+    R##op(CompactBufferReader &reader);                                 \
+                                                                        \
+  public:                                                               \
+    Opcode opcode() const {                                             \
+        return RInstruction::Recover_##op;                              \
+    }
+
 class RResumePoint MOZ_FINAL : public RInstruction
 {
   private:
     uint32_t pcOffset_;           // Offset from script->code.
     uint32_t numOperands_;        // Number of slots.
 
-    friend class RInstruction;
-    RResumePoint(CompactBufferReader &reader);
-
   public:
-    virtual Opcode opcode() const {
-        return Recover_ResumePoint;
-    }
+    RINSTRUCTION_HEADER_(ResumePoint)
 
     uint32_t pcOffset() const {
         return pcOffset_;
     }
     virtual uint32_t numOperands() const {
         return numOperands_;
     }
+    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/jit/Snapshots.cpp
+++ b/js/src/jit/Snapshots.cpp
@@ -71,16 +71,19 @@ using namespace js::jit;
 //         UNTYPED_REG_REG     [GPR_REG,      GPR_REG]
 //         UNTYPED_REG_STACK   [GPR_REG,      STACK_OFFSET]
 //         UNTYPED_STACK_REG   [STACK_OFFSET, GPR_REG]
 //         UNTYPED_STACK_STACK [STACK_OFFSET, STACK_OFFSET]
 //           Value with dynamically known type. On 32 bits architecture, the
 //           first register/stack-offset correspond to the holder of the type,
 //           and the second correspond to the payload of the JS Value.
 //
+//         RECOVER_INSTRUCTION [INDEX]
+//           Index into the list of recovered instruction results.
+//
 //         TYPED_REG [PACKED_TAG, GPR_REG]:
 //           Value with statically known type, which payload is stored in a
 //           register.
 //
 //         TYPED_STACK [PACKED_TAG, STACK_OFFSET]:
 //           Value with statically known type, which payload is stored at an
 //           offset on the stack.
 //
@@ -214,16 +217,25 @@ RValueAllocation::layoutFromMode(Mode mo
         static const RValueAllocation::Layout layout = {
             PAYLOAD_STACK_OFFSET,
             PAYLOAD_NONE,
             "value"
         };
         return layout;
       }
 #endif
+      case RECOVER_INSTRUCTION: {
+        static const RValueAllocation::Layout layout = {
+            PAYLOAD_INDEX,
+            PAYLOAD_NONE,
+            "instruction"
+        };
+        return layout;
+      }
+
       default: {
         static const RValueAllocation::Layout regLayout = {
             PAYLOAD_PACKED_TAG,
             PAYLOAD_GPR,
             "typed value"
         };
 
         static const RValueAllocation::Layout stackLayout = {
--- a/js/src/jit/Snapshots.h
+++ b/js/src/jit/Snapshots.h
@@ -49,16 +49,18 @@ class RValueAllocation
         UNTYPED_REG_REG     = 0x06,
         UNTYPED_REG_STACK   = 0x07,
         UNTYPED_STACK_REG   = 0x08,
         UNTYPED_STACK_STACK = 0x09,
 #elif defined(JS_PUNBOX64)
         UNTYPED_REG         = 0x06,
         UNTYPED_STACK       = 0x07,
 #endif
+        RECOVER_INSTRUCTION = 0x0a,
+
         // The JSValueType is packed in the Mode.
         TYPED_REG_MIN       = 0x10,
         TYPED_REG_MAX       = 0x17,
         TYPED_REG = TYPED_REG_MIN,
 
         // The JSValueType is packed in the Mode.
         TYPED_STACK_MIN     = 0x18,
         TYPED_STACK_MAX     = 0x1f,
@@ -231,16 +233,21 @@ class RValueAllocation
         return RValueAllocation(CST_NULL);
     }
 
     // CONSTANT's index
     static RValueAllocation ConstantPool(uint32_t index) {
         return RValueAllocation(CONSTANT, payloadOfIndex(index));
     }
 
+    // Recover instruction's index
+    static RValueAllocation RecoverInstruction(uint32_t index) {
+        return RValueAllocation(RECOVER_INSTRUCTION, payloadOfIndex(index));
+    }
+
     void writeHeader(CompactBufferWriter &writer, JSValueType type, uint32_t regCode) const;
   public:
     static RValueAllocation read(CompactBufferReader &reader);
     void write(CompactBufferWriter &writer) const;
 
   public:
     Mode mode() const {
         return mode_;
@@ -468,16 +475,23 @@ class RecoverReader
 
   private:
     void readRecoverHeader();
     void readInstruction();
 
   public:
     RecoverReader(SnapshotReader &snapshot, const uint8_t *recovers, uint32_t size);
 
+    uint32_t numInstructions() const {
+        return numInstructions_;
+    }
+    uint32_t numInstructionsRead() const {
+        return numInstructionsRead_;
+    }
+
     bool moreInstructions() const {
         return numInstructionsRead_ < numInstructions_;
     }
     void nextInstruction() {
         readInstruction();
     }
 
     const RInstruction *instruction() const {
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -138,23 +138,41 @@ ToStackIndex(LAllocation *a)
 
 bool
 CodeGeneratorShared::encodeAllocation(LSnapshot *snapshot, MDefinition *mir,
                                        uint32_t *allocIndex)
 {
     if (mir->isBox())
         mir = mir->toBox()->getOperand(0);
 
-    MIRType type = mir->isUnused()
-        ? MIRType_MagicOptimizedOut
-        : mir->type();
+    MIRType type =
+        mir->isRecoveredOnBailout() ? MIRType_None :
+        mir->isUnused() ? MIRType_MagicOptimizedOut :
+        mir->type();
 
     RValueAllocation alloc;
 
     switch (type) {
+      case MIRType_None:
+      {
+        MOZ_ASSERT(mir->isRecoveredOnBailout());
+        uint32_t index = 0;
+        LRecoverInfo *recoverInfo = snapshot->recoverInfo();
+        MNode **it = recoverInfo->begin(), **end = recoverInfo->end();
+        while (it != end && mir != *it) {
+            ++it;
+            ++index;
+        }
+
+        // This MDefinition is recovered, thus it should be listed in the
+        // LRecoverInfo.
+        MOZ_ASSERT(it != end && mir == *it);
+        alloc = RValueAllocation::RecoverInstruction(index);
+        break;
+      }
       case MIRType_Undefined:
         alloc = RValueAllocation::Undefined();
         break;
       case MIRType_Null:
         alloc = RValueAllocation::Null();
         break;
       case MIRType_Int32:
       case MIRType_String: