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 181118 7977e7f8a0948976f4fbf3b4bdce74cff52dca1e
parent 181117 85b6c3b4b26da80194e63cdc2f1692c13b4b69b2
child 181119 8a3e7ed0c4c1c56bfe8547915e3a05a0c651d080
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjandem
bugs990106
milestone32.0a1
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: