Bug 1073033 part 2.1 - InlineFrameIterator: Recover the non-default value of a function. r=shu
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Fri, 19 Dec 2014 15:28:29 +0100
changeset 220599 c7733d2b62712ed0b0e3e3597bb9db9f8036041c
parent 220598 0adb9a599879d6546da03e0950b1957555adcc1a
child 220600 c2564ebc773352889c89f89e4107fae11182d411
push id10503
push userryanvm@gmail.com
push dateFri, 19 Dec 2014 20:13:42 +0000
treeherderfx-team@98ee95ac6be5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs1073033
milestone37.0a1
Bug 1073033 part 2.1 - InlineFrameIterator: Recover the non-default value of a function. r=shu
js/src/jit/JitFrameIterator.h
js/src/jit/JitFrames.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -493,36 +493,30 @@ class SnapshotIterator
     Value read() {
         return allocationValue(readAllocation());
     }
 
     // Read the |Normal| value unless it is not available and that the snapshot
     // provides a |Default| value. This is useful to avoid invalidations of the
     // frame while we are only interested in a few properties which are provided
     // by the |Default| value.
-    Value readWithDefault() {
-        return allocationValue(readAllocation(), RM_NormalOrDefault);
-    }
-
-    Value maybeRead(MaybeReadFallback &fallback) {
+    Value readWithDefault(RValueAllocation *alloc) {
+        *alloc = RValueAllocation();
         RValueAllocation a = readAllocation();
         if (allocationReadable(a))
             return allocationValue(a);
 
-        if (fallback.canRecoverResults()) {
-            if (!initInstructionResults(fallback))
-                js::CrashAtUnhandlableOOM("Unable to recover allocations.");
+        *alloc = a;
+        return allocationValue(a, RM_AlwaysDefault);
+    }
 
-            if (allocationReadable(a))
-                return allocationValue(a);
-
-            MOZ_ASSERT_UNREACHABLE("All allocations should be readable.");
-        }
-
-        return fallback.unreadablePlaceholder();
+    Value maybeRead(const RValueAllocation &a, MaybeReadFallback &fallback);
+    Value maybeRead(MaybeReadFallback &fallback) {
+        RValueAllocation a = readAllocation();
+        return maybeRead(a, fallback);
     }
 
     void traceAllocation(JSTracer *trc);
 
     void readCommonFrameSlots(Value *scopeChain, Value *rval, MaybeReadFallback &fallback) {
         if (scopeChain)
             *scopeChain = maybeRead(fallback);
         else
@@ -591,17 +585,26 @@ class InlineFrameIterator
     uint32_t framesRead_;
 
     // When the inline-frame-iterator is created, this variable is defined to
     // UINT32_MAX. Then the first iteration of findNextFrame, which settle on
     // the innermost frame, is used to update this counter to the number of
     // frames contained in the recover buffer.
     uint32_t frameCount_;
 
-    RootedFunction callee_;
+    // The |calleeTemplate_| fields contains either the JSFunction or the
+    // template from which it is supposed to be cloned. The |calleeRVA_| is an
+    // Invalid value allocation, if the |calleeTemplate_| field is the effective
+    // JSFunction, and not its template. On the other hand, any other value
+    // allocation implies that the |calleeTemplate_| is the template JSFunction
+    // from which the effective one would be derived and cached by the Recover
+    // instruction result.
+    RootedFunction calleeTemplate_;
+    RValueAllocation calleeRVA_;
+
     RootedScript script_;
     jsbytecode *pc_;
     uint32_t numActualArgs_;
 
     struct Nop {
         void operator()(const Value &v) { }
     };
 
@@ -612,24 +615,34 @@ class InlineFrameIterator
   public:
     InlineFrameIterator(ThreadSafeContext *cx, const JitFrameIterator *iter);
     InlineFrameIterator(JSRuntime *rt, const JitFrameIterator *iter);
     InlineFrameIterator(ThreadSafeContext *cx, const InlineFrameIterator *iter);
 
     bool more() const {
         return frame_ && framesRead_ < frameCount_;
     }
-    JSFunction *callee() const {
-        MOZ_ASSERT(callee_);
-        return callee_;
+
+    // Due to optimizations, we are not always capable of reading the callee of
+    // inlined frames without invalidating the IonCode. This function might
+    // return either the effective callee of the JSFunction which might be used
+    // to create it.
+    //
+    // As such, the |calleeTemplate()| can be used to read most of the metadata
+    // which are conserved across clones.
+    JSFunction *calleeTemplate() const {
+        MOZ_ASSERT(isFunctionFrame());
+        return calleeTemplate_;
     }
-    JSFunction *maybeCallee() const {
-        return callee_;
+    JSFunction *maybeCalleeTemplate() const {
+        return calleeTemplate_;
     }
 
+    JSFunction *callee(MaybeReadFallback &fallback) const;
+
     unsigned numActualArgs() const {
         // The number of actual arguments of inline frames is recovered by the
         // iteration process. It is recovered from the bytecode because this
         // property still hold since the for inlined frames. This property does not
         // hold for the parent frame because it can have optimize a call to
         // js_fun_call or js_fun_apply.
         if (more())
             return numActualArgs_;
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -1895,16 +1895,35 @@ SnapshotIterator::allocationValue(const 
         MOZ_ASSERT(rm & RM_AlwaysDefault);
         return ionScript_->getConstant(alloc.index2());
 
       default:
         MOZ_CRASH("huh?");
     }
 }
 
+Value
+SnapshotIterator::maybeRead(const RValueAllocation &a, MaybeReadFallback &fallback)
+{
+    if (allocationReadable(a))
+        return allocationValue(a);
+
+    if (fallback.canRecoverResults()) {
+        if (!initInstructionResults(fallback))
+            js::CrashAtUnhandlableOOM("Unable to recover allocations.");
+
+        if (allocationReadable(a))
+            return allocationValue(a);
+
+        MOZ_ASSERT_UNREACHABLE("All allocations should be readable.");
+    }
+
+    return fallback.unreadablePlaceholder();
+}
+
 void
 SnapshotIterator::writeAllocationValuePayload(const RValueAllocation &alloc, Value v)
 {
     uintptr_t payload = *v.payloadUIntPtr();
 #if defined(JS_PUNBOX64)
     // Do not write back the tag, as this will trigger an assertion when we will
     // reconstruct the JS Value while marking again or when bailing out.
     payload &= JSVAL_PAYLOAD_MASK;
@@ -2227,34 +2246,37 @@ const OsiIndex *
 JitFrameIterator::osiIndex() const
 {
     MOZ_ASSERT(isIonJS());
     SafepointReader reader(ionScript(), safepoint());
     return ionScript()->getOsiIndex(reader.osiReturnPointOffset());
 }
 
 InlineFrameIterator::InlineFrameIterator(ThreadSafeContext *cx, const JitFrameIterator *iter)
-  : callee_(cx),
+  : calleeTemplate_(cx),
+    calleeRVA_(),
     script_(cx)
 {
     resetOn(iter);
 }
 
 InlineFrameIterator::InlineFrameIterator(JSRuntime *rt, const JitFrameIterator *iter)
-  : callee_(rt),
+  : calleeTemplate_(rt),
+    calleeRVA_(),
     script_(rt)
 {
     resetOn(iter);
 }
 
 InlineFrameIterator::InlineFrameIterator(ThreadSafeContext *cx, const InlineFrameIterator *iter)
   : frame_(iter ? iter->frame_ : nullptr),
     framesRead_(0),
     frameCount_(iter ? iter->frameCount_ : UINT32_MAX),
-    callee_(cx),
+    calleeTemplate_(cx),
+    calleeRVA_(),
     script_(cx)
 {
     if (frame_) {
         start_ = SnapshotIterator(*frame_);
 
         // findNextFrame will iterate to the next frame and init. everything.
         // Therefore to settle on the same frame, we report one frame less readed.
         framesRead_ = iter->framesRead_ - 1;
@@ -2278,17 +2300,18 @@ InlineFrameIterator::resetOn(const JitFr
 void
 InlineFrameIterator::findNextFrame()
 {
     MOZ_ASSERT(more());
 
     si_ = start_;
 
     // Read the initial frame out of the C stack.
-    callee_ = frame_->maybeCallee();
+    calleeTemplate_ = frame_->maybeCallee();
+    calleeRVA_ = RValueAllocation();
     script_ = frame_->script();
     MOZ_ASSERT(script_->hasBaselineScript());
 
     // Settle on the outermost frame without evaluating any instructions before
     // looking for a pc.
     si_.settleOnFrame();
 
     pc_ = script_->offsetToPC(si_.pcOffset());
@@ -2327,46 +2350,59 @@ InlineFrameIterator::findNextFrame()
         for (unsigned j = 0; j < skipCount; j++)
             si_.skip();
 
         // This value should correspond to the function which is being inlined.
         // The value must be readable to iterate over the inline frame. Most of
         // the time, these functions are stored as JSFunction constants,
         // register which are holding the JSFunction pointer, or recover
         // instruction with Default value.
-        Value funval = si_.readWithDefault();
+        Value funval = si_.readWithDefault(&calleeRVA_);
 
         // Skip extra value allocations.
         while (si_.moreAllocations())
             si_.skip();
 
         si_.nextFrame();
 
-        callee_ = &funval.toObject().as<JSFunction>();
+        calleeTemplate_ = &funval.toObject().as<JSFunction>();
 
         // Inlined functions may be clones that still point to the lazy script
         // for the executed script, if they are clones. The actual script
         // exists though, just make sure the function points to it.
-        script_ = callee_->existingScriptForInlinedFunction();
+        script_ = calleeTemplate_->existingScriptForInlinedFunction();
         MOZ_ASSERT(script_->hasBaselineScript());
 
         pc_ = script_->offsetToPC(si_.pcOffset());
     }
 
     // The first time we do not know the number of frames, we only settle on the
     // last frame, and update the number of frames based on the number of
     // iteration that we have done.
     if (frameCount_ == UINT32_MAX) {
         MOZ_ASSERT(!si_.moreFrames());
         frameCount_ = i;
     }
 
     framesRead_++;
 }
 
+JSFunction *
+InlineFrameIterator::callee(MaybeReadFallback &fallback) const
+{
+    MOZ_ASSERT(isFunctionFrame());
+    if (calleeRVA_.mode() == RValueAllocation::INVALID || !fallback.canRecoverResults())
+        return calleeTemplate_;
+
+    SnapshotIterator s(si_);
+    // :TODO: Handle allocation failures from recover instruction.
+    Value funval = s.maybeRead(calleeRVA_, fallback);
+    return &funval.toObject().as<JSFunction>();
+}
+
 JSObject *
 InlineFrameIterator::computeScopeChain(Value scopeChainValue, bool *hasCallObj) const
 {
     if (scopeChainValue.isObject()) {
         if (hasCallObj)
             *hasCallObj = isFunctionFrame() && callee()->isHeavyweight();
         return &scopeChainValue.toObject();
     }
@@ -2381,17 +2417,17 @@ InlineFrameIterator::computeScopeChain(V
     MOZ_ASSERT(!script()->isForEval());
     MOZ_ASSERT(script()->compileAndGo());
     return &script()->global();
 }
 
 bool
 InlineFrameIterator::isFunctionFrame() const
 {
-    return !!callee_;
+    return !!calleeTemplate_;
 }
 
 MachineState
 MachineState::FromBailout(mozilla::Array<uintptr_t, Registers::Total> &regs,
                           mozilla::Array<double, FloatRegisters::TotalPhys> &fpregs)
 {
     MachineState machine;
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1994,26 +1994,32 @@ js::NewFunctionWithProto(ExclusiveContex
     }
     if (allocKind == JSFunction::ExtendedFinalizeKind)
         fun->initializeExtended();
     fun->initAtom(atom);
 
     return fun;
 }
 
+bool
+js::CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun)
+{
+    return compartment == fun->compartment() &&
+           !fun->hasSingletonType() &&
+           !types::UseNewTypeForClone(fun);
+}
+
 JSFunction *
 js::CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, gc::AllocKind allocKind,
                         NewObjectKind newKindArg /* = GenericObject */)
 {
     MOZ_ASSERT(parent);
     MOZ_ASSERT(!fun->isBoundFunction());
 
-    bool useSameScript = cx->compartment() == fun->compartment() &&
-                         !fun->hasSingletonType() &&
-                         !types::UseNewTypeForClone(fun);
+    bool useSameScript = CloneFunctionObjectUseSameScript(cx->compartment(), fun);
 
     if (!useSameScript && fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
         return nullptr;
 
     NewObjectKind newKind = useSameScript ? newKindArg : SingletonObject;
     JSObject *cloneProto = nullptr;
     if (fun->isStarGenerator()) {
         cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -50,17 +50,19 @@ class JSFunction : public js::NativeObje
         INTERPRETED_LAZY = 0x1000,  /* function is interpreted but doesn't have a script yet */
         ARROW            = 0x2000,  /* ES6 '(args) => body' syntax */
 
         /* Derived Flags values for convenience: */
         NATIVE_FUN = 0,
         ASMJS_CTOR = ASMJS | NATIVE_CTOR,
         ASMJS_LAMBDA_CTOR = ASMJS | NATIVE_CTOR | LAMBDA,
         INTERPRETED_LAMBDA = INTERPRETED | LAMBDA,
-        INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW
+        INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW,
+        STABLE_ACROSS_CLONES = NATIVE_CTOR | IS_FUN_PROTO | EXPR_CLOSURE | HAS_GUESSED_ATOM |
+                               LAMBDA | SELF_HOSTED | SELF_HOSTED_CTOR | HAS_REST | ASMJS | ARROW
     };
 
     static_assert(INTERPRETED == JS_FUNCTION_INTERPRETED_BIT,
                   "jsfriendapi.h's JSFunction::INTERPRETED-alike is wrong");
 
   private:
     uint16_t        nargs_;       /* number of formal arguments
                                      (including defaults and the rest parameter unlike f.length) */
@@ -573,16 +575,19 @@ class FunctionExtended : public JSFuncti
 
   private:
     friend class JSFunction;
 
     /* Reserved slots available for storage by particular native functions. */
     HeapValue extendedSlots[NUM_EXTENDED_SLOTS];
 };
 
+extern bool
+CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun);
+
 extern JSFunction *
 CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
                     gc::AllocKind kind = JSFunction::FinalizeKind,
                     NewObjectKind newKindArg = GenericObject);
 
 extern bool
 FindBody(JSContext *cx, HandleFunction fun, HandleLinearString src, size_t *bodyStart,
          size_t *bodyEnd);
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1057,50 +1057,86 @@ FrameIter::updatePcQuadratic()
             return;
         }
         break;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSFunction *
-FrameIter::callee() const
+FrameIter::calleeTemplate() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case INTERP:
         MOZ_ASSERT(isFunctionFrame());
         return &interpFrame()->callee();
       case JIT:
         if (data_.jitFrames_.isBaselineJS())
             return data_.jitFrames_.callee();
         MOZ_ASSERT(data_.jitFrames_.isIonScripted());
-        return ionInlineFrames_.callee();
+        return ionInlineFrames_.calleeTemplate();
     }
     MOZ_CRASH("Unexpected state");
 }
 
-Value
-FrameIter::calleev() const
+JSFunction *
+FrameIter::callee(JSContext *cx) const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case INTERP:
-        MOZ_ASSERT(isFunctionFrame());
-        return interpFrame()->calleev();
+        return calleeTemplate();
       case JIT:
-        return ObjectValue(*callee());
+        if (data_.jitFrames_.isIonScripted()) {
+            jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_);
+            return ionInlineFrames_.callee(recover);
+        }
+        MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
+        return calleeTemplate();
     }
     MOZ_CRASH("Unexpected state");
 }
 
+bool
+FrameIter::matchCallee(JSContext *cx, HandleFunction fun) const
+{
+    RootedFunction currentCallee(cx, calleeTemplate());
+
+    // As we do not know if the calleeTemplate is the real function, or the
+    // template from which it would be cloned, we compare properties which are
+    // stable across the cloning of JSFunctions.
+    if (((currentCallee->flags() ^ fun->flags()) & JSFunction::STABLE_ACROSS_CLONES) != 0 ||
+        currentCallee->nargs() != fun->nargs())
+    {
+        return false;
+    }
+
+    // Only some lambdas are optimized in a way which cannot be recovered without
+    // invalidating the frame. Thus, if one of the function is not a lambda we can just
+    // compare it against the calleeTemplate.
+    if (!fun->isLambda() || !currentCallee->isLambda())
+        return currentCallee == fun;
+
+    // Use the same condition as |js::CloneFunctionObject|, to know if we should
+    // expect both functions to have the same JSScript. If so, and if they are
+    // different, then they cannot be equal.
+    bool useSameScript = CloneFunctionObjectUseSameScript(fun->compartment(), currentCallee);
+    if (useSameScript && currentCallee->nonLazyScript() != fun->nonLazyScript())
+        return false;
+
+    // If none of the previous filters worked, then take the risk of
+    // invalidating the frame to identify the JSFunction.
+    return callee(cx) == fun;
+}
+
 unsigned
 FrameIter::numActualArgs() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case INTERP:
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1593,18 +1593,32 @@ class FrameIter
     // The following functions can only be called when hasScript()
     // -----------------------------------------------------------
 
     inline JSScript *script() const;
 
     bool        isConstructing() const;
     jsbytecode *pc() const { MOZ_ASSERT(!done()); return data_.pc_; }
     void        updatePcQuadratic();
-    JSFunction *callee() const;
-    Value       calleev() const;
+
+    // The function |calleeTemplate()| returns either the function from which
+    // the current |callee| was cloned or the |callee| if it can be read. As
+    // long as we do not have to investigate the scope chain or build a new
+    // frame, we should prefer to use |calleeTemplate| instead of |callee|, as
+    // requesting the |callee| might cause the invalidation of the frame. (see
+    // js::Lambda)
+    JSFunction *calleeTemplate() const;
+    JSFunction *callee(JSContext *cx) const;
+
+    JSFunction *maybeCallee(JSContext *cx) const {
+        return isFunctionFrame() ? callee(cx) : nullptr;
+    }
+
+    bool        matchCallee(JSContext *cx, HandleFunction fun) const;
+
     unsigned    numActualArgs() const;
     unsigned    numFormalArgs() const;
     Value       unaliasedActual(unsigned i, MaybeCheckAliasing = CHECK_ALIASING) const;
     template <class Op> inline void unaliasedForEachActual(JSContext *cx, Op op);
 
     JSObject   *scopeChain(JSContext *cx) const;
     CallObject &callObj(JSContext *cx) const;
 
@@ -1621,20 +1635,16 @@ class FrameIter
     // Both methods exist because of speed. thisv() will never rematerialize
     // an Ion frame, whereas computedThisValue() will.
     Value       computedThisValue() const;
     Value       thisv(JSContext *cx) const;
 
     Value       returnValue() const;
     void        setReturnValue(const Value &v);
 
-    JSFunction *maybeCallee() const {
-        return isFunctionFrame() ? callee() : nullptr;
-    }
-
     // These are only valid for the top frame.
     size_t      numFrameSlots() const;
     Value       frameSlotValue(size_t index) const;
 
     // Ensures that we have rematerialized the top frame and its associated
     // inline frames. Can only be called when isIon().
     bool ensureHasRematerializedFrame(JSContext *cx);