Bug 757932 - Inline calls to common property getters and setters. r=dvander,bhackett
authorEric Faust <efaust@mozilla.com>
Mon, 25 Jun 2012 16:06:55 -0700
changeset 106459 8862e62cd1f5212b4a71b51d0a5bc75e115184b2
parent 106458 d85ca085f35d180135e7e3d556c872eba499691f
child 106460 214075237f495f12c7f879ee19428be1563864a1
push id23447
push userdanderson@mozilla.com
push dateTue, 11 Sep 2012 17:34:27 +0000
treeherdermozilla-central@fdfaef738a00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander, bhackett
bugs757932
milestone16.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 757932 - Inline calls to common property getters and setters. r=dvander,bhackett
js/src/ion/IonBuilder.cpp
js/src/ion/IonBuilder.h
js/src/ion/IonFrames.cpp
js/src/ion/TypeOracle.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsopcode.h
js/src/jsopcode.tbl
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -740,17 +740,17 @@ IonBuilder::inspectOpcode(JSOp op)
       case JSOP_URSH:
         return jsop_bitop(op);
 
       case JSOP_ADD:
       case JSOP_SUB:
       case JSOP_MUL:
       case JSOP_DIV:
       case JSOP_MOD:
-      	return jsop_binary(op);
+        return jsop_binary(op);
 
       case JSOP_POS:
         return jsop_pos();
 
       case JSOP_NEG:
         return jsop_neg();
 
       case JSOP_AND:
@@ -2115,19 +2115,19 @@ IonBuilder::tableSwitch(JSOp op, jssrcno
         // lookupswitch, even if not all numbers are consecutive.
         // In that case this block goes to the default case
         if (casepc == pc) {
             caseblock->end(MGoto::New(defaultcase));
             defaultcase->addPredecessor(caseblock);
         }
 
         tableswitch->addCase(caseblock);
-        
+
         // If this is an actual case (not filled gap),
-        // add this block to the list that still needs to get processed 
+        // add this block to the list that still needs to get processed
         if (casepc != pc)
             tableswitch->addBlock(caseblock);
 
         pc2 += JUMP_OFFSET_LEN;
     }
 
     // Move defaultcase to the end, to maintain RPO.
     graph_.moveBlockToEnd(defaultcase);
@@ -2728,17 +2728,17 @@ IonBuilder::jsop_call_inline(JSFunction 
 {
 #ifdef DEBUG
     uint32 origStackDepth = current->stackDepth();
 #endif
 
     // |top| jumps into the callee subgraph -- save it for later use.
     MBasicBlock *top = current;
 
-    // Add the function constant before the resume point whihc map call
+    // Add the function constant before the resume point which map call
     // arguments.
     MConstant *constFun = MConstant::New(ObjectValue(*callee));
     current->add(constFun);
 
     // This resume point collects outer variables only.  It is used to recover
     // the stack state before the current bytecode.
     MResumePoint *inlineResumePoint = MResumePoint::New(top, pc, callerResumePoint_,
                                                         MResumePoint::Outer);
@@ -2753,31 +2753,32 @@ IonBuilder::jsop_call_inline(JSFunction 
     // duration of the call.
     MDefinitionVector argv;
 
     // Arguments are popped right-to-left so we have to fill |args| backwards.
     if (!discardCallArgs(argc, argv, top))
         return false;
 
     // Replace the potential object load by the corresponding constant version
-    // which is inlined here.
+    // which is inlined here. We do this to ensure that on a bailout, we can get
+    // back to the caller by iterating over the stack.
     inlineResumePoint->replaceOperand(inlineResumePoint->numOperands() - (argc + 2), constFun);
     current->pop();
     current->push(constFun);
 
     MDefinition *thisDefn = argv[0];
 
     // Build the graph.
     if (!inlineBuilder.buildInline(this, inlineResumePoint, thisDefn, argv))
         return false;
 
     MIRGraphExits &exits = *inlineBuilder.graph().exitAccumulator();
 
     // Create a |bottom| block for all the callee subgraph exits to jump to.
-    JS_ASSERT(*pc == JSOP_CALL);
+    JS_ASSERT(types::IsInlinableCall(pc));
     jsbytecode *postCall = GetNextPc(pc);
     MBasicBlock *bottom = newBlock(NULL, postCall);
     bottom->setCallerResumePoint(callerResumePoint_);
 
     // Link graph exits to |bottom| via MGotos, replacing MReturns.
     Vector<MDefinition *, 8, IonAllocPolicy> retvalDefns;
     for (MBasicBlock **it = exits.begin(), **end = exits.end(); it != end; ++it) {
         MBasicBlock *exitBlock = *it;
@@ -3134,21 +3135,21 @@ IonBuilder::jsop_funcall(uint32 argc)
         argc -= 1; 
     }
 
     // Call without inlining.
     return makeCall(target, argc, false);
 }
 
 bool
-IonBuilder::jsop_call(uint32 argc, bool constructing)
+IonBuilder::jsop_call_fun_barrier(HandleFunction target, uint32 argc, 
+                                  bool constructing,
+                                  types::TypeSet *types,
+                                  types::TypeSet *barrier)
 {
-    // Acquire known call target if existent.
-    RootedFunction target(cx, getSingleCallTarget(argc, pc));
-
     // Attempt to inline native and scripted functions.
     if (inliningEnabled() && target) {
         if (target->isNative()) {
             switch (inlineNativeCall(target, argc, constructing)) {
               case InliningStatus_Inlined:
                 return true;
               case InliningStatus_Error:
                 return false;
@@ -3156,21 +3157,34 @@ IonBuilder::jsop_call(uint32 argc, bool 
                 break;
             }
         }
 
         if (!constructing && makeInliningDecision(target))
             return inlineScriptedCall(target, argc);
     }
 
-    return makeCall(target, argc, constructing);
+    return makeCallBarrier(target, argc, constructing, types, barrier);
 }
 
 bool
-IonBuilder::makeCall(HandleFunction target, uint32 argc, bool constructing)
+IonBuilder::jsop_call(uint32 argc, bool constructing)
+{
+    // Acquire known call target if existent.
+    RootedFunction target(cx, getSingleCallTarget(argc, pc));
+    types::TypeSet *barrier;
+    types::TypeSet *types = oracle->returnTypeSet(script, pc, &barrier);
+    return jsop_call_fun_barrier(target, argc, constructing, types, barrier);
+}
+
+bool
+IonBuilder::makeCallBarrier(HandleFunction target, uint32 argc,
+                            bool constructing,
+                            types::TypeSet *types,
+                            types::TypeSet *barrier)
 {
     // This function may be called with mutated stack.
     // Querying TI for popped types is invalid.
 
     uint32 targetArgs = argc;
 
     // Collect number of missing arguments provided that the target is
     // scripted. Native functions are passed an explicit 'argc' parameter.
@@ -3224,19 +3238,25 @@ IonBuilder::makeCall(HandleFunction targ
     call->addArg(0, thisArg);
     call->initFunction(current->pop());
 
     current->add(call);
     current->push(call);
     if (!resumeAfter(call))
         return false;
 
+    return pushTypeBarrier(call, types, barrier);
+}
+
+bool
+IonBuilder::makeCall(HandleFunction target, uint32 argc, bool constructing)
+{
     types::TypeSet *barrier;
     types::TypeSet *types = oracle->returnTypeSet(script, pc, &barrier);
-    return pushTypeBarrier(call, types, barrier);
+    return makeCallBarrier(target, argc, constructing, types, barrier);
 }
 
 bool
 IonBuilder::jsop_incslot(JSOp op, uint32 slot)
 {
     int32 amt = (js_CodeSpec[op].format & JOF_INC) ? 1 : -1;
     bool post = !!(js_CodeSpec[op].format & JOF_POST);
     TypeOracle::Binary b = oracle->binaryOp(script, pc);
@@ -4598,16 +4618,151 @@ IonBuilder::jsop_not()
     MDefinition *value = current->pop();
 
     MNot *ins = new MNot(value);
     current->add(ins);
     current->push(ins);
     return true;
 }
 
+
+inline bool
+IonBuilder::TestCommonPropFunc(JSContext *cx, types::TypeSet *types, HandleId id,
+                   JSFunction **funcp, bool isGetter)
+{
+    JSObject *found = NULL;
+    JSObject *foundProto = NULL;
+    Shape *protoShape = NULL;
+
+    *funcp = NULL;
+
+    // No sense looking if we don't know what's going on.
+    if (!types || types->unknownObject())
+        return true;
+
+    // Iterate down all the types to see if they all have the same getter or
+    // setter.
+    for (unsigned i = 0; i < types->getObjectCount(); i++) {
+        JSObject *curObj = types->getSingleObject(i);
+
+        // Non-Singleton type
+        if (!curObj) {
+            types::TypeObject *typeObj = types->getTypeObject(i);
+
+            if (!typeObj)
+                continue;
+
+            if (typeObj->unknownProperties())
+                return true;
+
+            // If the type has an own property, we can't be sure we don't shadow
+            // the chain.
+            jsid typeId = types::MakeTypeId(cx, id);
+            types::TypeSet *propSet = typeObj->getProperty(cx, typeId, false);
+            if (!propSet)
+                return false;
+            if (propSet->isOwnProperty(false))
+                return true;
+
+            // Otherwise try using the prototype.
+            curObj = typeObj->proto;
+        }
+
+        // Turns out that we need to check for a property lookup op, else we
+        // will end up calling it mid-compilation.
+        JSObject *walker = curObj;
+        while (walker) {
+            if (walker->getClass()->ops.lookupProperty)
+                return true;
+            walker = walker->getProto();
+        }
+
+        JSObject *proto;
+        JSProperty *prop;
+
+        if (!curObj->lookupGeneric(cx, id, &proto, &prop))
+            return false;
+        if (!prop)
+            return true;
+
+        Shape *shape = (Shape *)prop;
+
+        // We want to optimize specialized getters/setters. The defaults will
+        // hit the slot optimization.
+        if (isGetter) {
+            if (shape->hasDefaultGetter() || !shape->hasGetterValue())
+                return true;
+        } else {
+            if (shape->hasDefaultSetter() || !shape->hasSetterValue())
+                return true;
+        }
+
+        JSObject * curFound = isGetter ? shape->getterObject():
+                                         shape->setterObject();
+
+        // Save the first seen, or verify uniqueness.
+        if (!found)
+            found = curFound;
+        else if (found != curFound)
+            return true;
+
+        // We only support cases with a single prototype shared. This is
+        // overwhelmingly more likely than having multiple different prototype
+        // chains with the same custom property function.
+        if (!foundProto) {
+            foundProto = proto;
+            protoShape = shape;
+        } else if (foundProto != proto)
+            return true;
+    }
+
+    // No need to add a freeze if we didn't find anything
+    if (!found)
+        return true;
+
+    JS_ASSERT(foundProto && protoShape);
+
+    types->addFreeze(cx);
+
+    MInstruction *wrapper = MConstant::New(ObjectValue(*foundProto));
+    current->add(wrapper);
+    MGuardShape *guard = MGuardShape::New(wrapper, protoShape);
+    current->add(guard);
+
+    // Now we have to freeze all the property typesets to ensure there isn't a
+    // lower shadowing getter or setter installed in the future.
+    types::TypeObject *curType;
+    for (unsigned i = 0; i < types->getObjectCount(); i++) {
+        curType = types->getTypeObject(i);
+
+        if (!curType) {
+            JSObject *obj = types->getSingleObject(i);
+            if (!obj)
+                continue;
+
+            curType = obj->getType(cx);
+        }
+
+        // Walk the prototype chain. Everyone has to have the property, since we
+        // just checked, so propSet cannot be NULL.
+        jsid typeId = types::MakeTypeId(cx, id);
+        types::TypeSet *propSet = curType->getProperty(cx, typeId, false);
+        JS_ASSERT(propSet);
+        while (!propSet->isOwnProperty(cx, curType, false)) {
+            curType = curType->proto->getType(cx);
+            propSet = curType->getProperty(cx, id, false);
+            JS_ASSERT(propSet);
+        }
+    }
+
+    *funcp = (JSFunction *)found;
+
+    return true;
+}
+
 bool
 IonBuilder::jsop_getprop(HandlePropertyName name)
 {
 
     LazyArgumentsType isArguments = oracle->propertyReadMagicArguments(script, pc);
     if (isArguments == MaybeArguments)
         return abort("Type is not definitely lazy arguments.");
     if (isArguments == DefinitelyArguments) {
@@ -4620,20 +4775,21 @@ IonBuilder::jsop_getprop(HandlePropertyN
     MInstruction *ins;
 
     types::TypeSet *barrier = oracle->propertyReadBarrier(script, pc);
     types::TypeSet *types = oracle->propertyRead(script, pc);
 
     TypeOracle::Unary unary = oracle->unaryOp(script, pc);
     TypeOracle::UnaryTypes unaryTypes = oracle->unaryTypes(script, pc);
 
+    RootedId id(cx, NameToId(name));
+
     JSObject *singleton = types ? types->getSingleton(cx) : NULL;
     if (singleton && !barrier) {
         bool isKnownConstant, testObject;
-        RootedId id(cx, NameToId(name));
         RootedObject global(cx, script->global());
         if (!TestSingletonPropertyTypes(cx, unaryTypes.inTypes,
                                         global, id,
                                         &isKnownConstant, &testObject))
         {
             return false;
         }
 
@@ -4658,16 +4814,33 @@ IonBuilder::jsop_getprop(HandlePropertyN
             fixed->setResultType(unary.rval);
 
         current->add(fixed);
         current->push(fixed);
 
         return pushTypeBarrier(fixed, types, barrier);
     }
 
+    // Attempt to inline common property getter. At least patch to call instead.
+    JSFunction *commonGetter;
+    if (!TestCommonPropFunc(cx, unaryTypes.inTypes, id, &commonGetter, true))
+        return false;
+    if (commonGetter) {
+        // Spoof stack to expected state for call.
+        pushConstant(ObjectValue(*commonGetter));
+
+        MPassArg *wrapper = MPassArg::New(obj);
+        current->push(wrapper);
+        current->add(wrapper);
+
+        RootedFunction getter(cx, commonGetter);
+
+        return makeCallBarrier(getter, 0, false, types, barrier);
+    }
+
     if (unary.ival == MIRType_Object) {
         MGetPropertyCache *load = MGetPropertyCache::New(obj, name);
         if (!barrier) {
             // Use the default type (Value) when the output type is undefined or null.
             // Because specializing to those types isn't possible.
             if (unary.rval != MIRType_Undefined && unary.rval != MIRType_Null)
                 load->setResultType(unary.rval);
         }
@@ -4703,25 +4876,46 @@ IonBuilder::jsop_setprop(HandlePropertyN
             current->add(fixed);
             current->push(value);
             if (propTypes->needsBarrier(cx))
                 fixed->setNeedsBarrier();
             return resumeAfter(fixed);
         }
     }
 
+    RootedId id(cx, NameToId(name));
+
+    JSFunction *commonSetter;
+    if (!TestCommonPropFunc(cx, binaryTypes.lhsTypes, id, &commonSetter, false))
+        return false;
+    if (!monitored && commonSetter) {
+        // Dummy up the stack, as in getprop
+        pushConstant(ObjectValue(*commonSetter));
+
+        MPassArg *wrapper = MPassArg::New(obj);
+        current->push(wrapper);
+        current->add(wrapper);
+
+        MPassArg *arg = MPassArg::New(value);
+        current->push(arg);
+        current->add(arg);
+
+        RootedFunction setter(cx, commonSetter);
+
+        return makeCallBarrier(setter, 1, false, NULL, NULL);
+    }
+
     oracle->binaryOp(script, pc);
 
     MSetPropertyInstruction *ins;
     if (monitored) {
         ins = MCallSetProperty::New(obj, value, name, script->strictModeCode);
     } else {
         ins = MSetPropertyCache::New(obj, value, name, script->strictModeCode);
 
-        RootedId id(cx, NameToId(name));
         if (!binaryTypes.lhsTypes || binaryTypes.lhsTypes->propertyNeedsBarrier(cx, id))
             ins->setNeedsBarrier();
     }
 
     current->add(ins);
     current->push(value);
 
     return resumeAfter(ins);
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -103,17 +103,17 @@ class IonBuilder : public MIRGenerator
             FOR_LOOP_UPDATE,    // for (; ; x) { }
             TABLE_SWITCH,       // switch() { x }
             LOOKUP_SWITCH,      // switch() { x }
             AND_OR              // && x, || x
         };
 
         State state;            // Current state of this control structure.
         jsbytecode *stopAt;     // Bytecode at which to stop the processing loop.
-        
+
         // For if structures, this contains branch information.
         union {
             struct {
                 MBasicBlock *ifFalse;
                 jsbytecode *falseEnd;
                 MBasicBlock *ifTrue;    // Set when the end of the true path is reached.
             } branch;
             struct {
@@ -382,16 +382,27 @@ class IonBuilder : public MIRGenerator
     InliningStatus inlineNativeCall(JSFunction *target, uint32 argc, bool constructing);
     InliningStatus inlineMathFunction(MMathFunction::Function function, MIRType argType,
                                       MIRType returnType);
 
     bool jsop_call_inline(JSFunction *callee, uint32 argc, IonBuilder &inlineBuilder);
     bool inlineScriptedCall(JSFunction *target, uint32 argc);
     bool makeInliningDecision(JSFunction *target);
 
+    bool jsop_call_fun_barrier(HandleFunction target, uint32 argc, 
+                               bool constructing,
+							   types::TypeSet *types,
+                               types::TypeSet *barrier);
+    bool makeCallBarrier(HandleFunction target, uint32 argc, bool constructing,
+                         types::TypeSet *types, types::TypeSet *barrier);
+
+    inline bool TestCommonPropFunc(JSContext *cx, types::TypeSet *types,
+                                   HandleId id, JSFunction **funcp, 
+                                   bool isGetter);
+
   public:
     // A builder is inextricably tied to a particular script.
     JSScript * const script;
 
   private:
     jsbytecode *pc;
     HandleObject initialScopeChain_;
     MBasicBlock *current;
--- a/js/src/ion/IonFrames.cpp
+++ b/js/src/ion/IonFrames.cpp
@@ -418,17 +418,17 @@ ReadAllocation(const IonFrameIterator &f
     uint8 *argv = reinterpret_cast<uint8 *>(frame.jsFrame()->argv());
     return *reinterpret_cast<uintptr_t *>(argv + index);
 }
 
 static void
 MarkIonJSFrame(JSTracer *trc, const IonFrameIterator &frame)
 {
     IonJSFrameLayout *layout = (IonJSFrameLayout *)frame.fp();
-    
+
     MarkCalleeToken(trc, layout->calleeToken());
 
     IonScript *ionScript;
     if (frame.checkInvalidation(&ionScript)) {
         // This frame has been invalidated, meaning that its IonScript is no
         // longer reachable through the callee token (JSFunction/JSScript->ion
         // is now NULL or recompiled). Manually trace it here.
         IonScript::Trace(trc, ionScript);
@@ -882,16 +882,20 @@ MachineState::FromBailout(uintptr_t regs
 bool
 InlineFrameIterator::isConstructing() const
 {
     // Skip the current frame and look at the caller's.
     if (more()) {
         InlineFrameIterator parent(*this);
         ++parent;
 
+        // Inlined Getters and Setters are never constructing.
+        if (IsGetterPC(parent.pc()) || IsSetterPC(parent.pc()))
+            return false;
+
         // In the case of a JS frame, look up the pc from the snapshot.
         JS_ASSERT(js_CodeSpec[*parent.pc()].format & JOF_INVOKE);
 
         return (JSOp)*parent.pc() == JSOP_NEW;
     }
 
     return frame_->isConstructing();
 }
@@ -904,16 +908,21 @@ IonFrameIterator::isConstructing() const
     // Skip the current frame and look at the caller's.
     do {
         ++parent;
     } while (!parent.done() && !parent.isScripted());
 
     if (parent.isScripted()) {
         // In the case of a JS frame, look up the pc from the snapshot.
         InlineFrameIterator inlinedParent(&parent);
+
+        //Inlined Getters and Setters are never constructing.
+        if (IsGetterPC(inlinedParent.pc()) || IsSetterPC(inlinedParent.pc()))
+            return false;
+
         JS_ASSERT(js_CodeSpec[*inlinedParent.pc()].format & JOF_INVOKE);
 
         return (JSOp)*inlinedParent.pc() == JSOP_NEW;
     }
 
     JS_ASSERT(parent.done());
     return activation_->entryfp()->isConstructing();
 }
--- a/js/src/ion/TypeOracle.cpp
+++ b/js/src/ion/TypeOracle.cpp
@@ -485,17 +485,17 @@ TypeSet *
 TypeInferenceOracle::getCallReturn(JSScript *script, jsbytecode *pc)
 {
     return script->analysis()->pushedTypes(pc, 0);
 }
 
 bool
 TypeInferenceOracle::canInlineCall(JSScript *caller, jsbytecode *pc)
 {
-    JS_ASSERT(JSOp(*pc) == JSOP_CALL || JSOp(*pc) == JSOP_FUNCALL || JSOp(*pc) == JSOP_FUNAPPLY);
+    JS_ASSERT(types::IsInlinableCall(pc));
 
     Bytecode *code = caller->analysis()->maybeCode(pc);
     if (code->monitoredTypes || code->monitoredTypesReturn || caller->analysis()->typeBarriers(cx, pc))
         return false;
     return true;
 }
 
 bool
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -507,16 +507,19 @@ struct TypeResult
     Type type;
     TypeResult *next;
 
     TypeResult(uint32_t offset, Type type)
         : offset(offset), type(type), next(NULL)
     {}
 };
 
+/* Is this a reasonable PC to be doing inlining on? */
+inline bool isInlinableCall(jsbytecode *pc);
+
 /*
  * Type barriers overview.
  *
  * Type barriers are a technique for using dynamic type information to improve
  * the inferred types within scripts. At certain opcodes --- those with the
  * JOF_TYPESET format --- we will construct a type set storing the set of types
  * which we have observed to be pushed at that opcode, and will only use those
  * observed types when doing propagation downstream from the bytecode. For
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -154,16 +154,31 @@ TypeIdString(jsid id)
 {
 #ifdef DEBUG
     return TypeIdStringImpl(id);
 #else
     return "(missing)";
 #endif
 }
 
+/* Assert code to know which PCs are reasonable to be considering inlining on */
+inline bool
+IsInlinableCall(jsbytecode *pc)
+{
+    JSOp op = JSOp(*pc);
+
+    // CALL, FUNCALL, FUNAPPLY (Standard callsites)
+    // GETPROP, CALLPROP, and LENGTH. (Inlined Getters)
+    // SETPROP, SETNAME, SETGNAME (Inlined Setters)
+    return op == JSOP_CALL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY ||
+           op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH ||
+           op == JSOP_SETPROP || op == JSOP_SETGNAME || op == JSOP_SETNAME;
+
+}
+
 /*
  * Structure for type inference entry point functions. All functions which can
  * change type information must use this, and functions which depend on
  * intermediate types (i.e. JITs) can use this to ensure that intermediate
  * information is not collected and does not change.
  *
  * Pins inference results so that intermediate type information, TypeObjects
  * and JSScripts won't be collected during GC. Does additional sanity checking
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -481,16 +481,29 @@ IsValidBytecodeOffset(JSContext *cx, JSS
 inline bool
 FlowsIntoNext(JSOp op)
 {
     /* JSOP_YIELD is considered to flow into the next instruction, like JSOP_CALL. */
     return op != JSOP_STOP && op != JSOP_RETURN && op != JSOP_RETRVAL && op != JSOP_THROW &&
            op != JSOP_GOTO && op != JSOP_RETSUB;
 }
 
+inline bool
+IsGetterPC(jsbytecode *pc)
+{
+    JSOp op = JSOp(*pc);
+    return op == JSOP_LENGTH  || op == JSOP_GETPROP || op == JSOP_CALLPROP;
+}
+
+inline bool
+IsSetterPC(jsbytecode *pc)
+{
+    JSOp op = JSOp(*pc);
+    return op == JSOP_SETPROP || op == JSOP_SETNAME || op == JSOP_SETGNAME;
+}
 /*
  * Counts accumulated for a single opcode in a script. The counts tracked vary
  * between opcodes, and this structure ensures that counts are accessed in a
  * coherent fashion.
  */
 class PCCounts
 {
     friend struct ::JSScript;
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -135,17 +135,17 @@ OPDEF(JSOP_DECELEM,   46, "decelem",    
 OPDEF(JSOP_NAMEINC,   47, "nameinc",    NULL,         6,  0,  1, 15,  JOF_ATOM|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
 OPDEF(JSOP_PROPINC,   48, "propinc",    NULL,         6,  1,  1, 15,  JOF_ATOM|JOF_PROP|JOF_INC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
 OPDEF(JSOP_ELEMINC,   49, "eleminc",    NULL,         2,  2,  1, 15,  JOF_BYTE |JOF_ELEM|JOF_INC|JOF_POST|JOF_TMPSLOT2|JOF_DECOMPOSE)
 OPDEF(JSOP_NAMEDEC,   50, "namedec",    NULL,         6,  0,  1, 15,  JOF_ATOM|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
 OPDEF(JSOP_PROPDEC,   51, "propdec",    NULL,         6,  1,  1, 15,  JOF_ATOM|JOF_PROP|JOF_DEC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
 OPDEF(JSOP_ELEMDEC,   52, "elemdec",    NULL,         2,  2,  1, 15,  JOF_BYTE |JOF_ELEM|JOF_DEC|JOF_POST|JOF_TMPSLOT2|JOF_DECOMPOSE)
 
 OPDEF(JSOP_GETPROP,   53, "getprop",    NULL,         5,  1,  1, 18,  JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_TMPSLOT3)
-OPDEF(JSOP_SETPROP,   54, "setprop",    NULL,         5,  2,  1,  3,  JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
+OPDEF(JSOP_SETPROP,   54, "setprop",    NULL,         5,  2,  1,  3,  JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING|JOF_TMPSLOT)
 OPDEF(JSOP_GETELEM,   55, "getelem",    NULL,         1,  2,  1, 18,  JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC)
 OPDEF(JSOP_SETELEM,   56, "setelem",    NULL,         1,  3,  1,  3,  JOF_BYTE |JOF_ELEM|JOF_SET|JOF_DETECTING)
 OPDEF(JSOP_CALLNAME,  57, "callname",   NULL,         5,  0,  1, 19,  JOF_ATOM|JOF_NAME|JOF_TYPESET)
 OPDEF(JSOP_CALL,      58, "call",       NULL,         3, -1,  1, 18,  JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
 OPDEF(JSOP_NAME,      59, "name",       NULL,         5,  0,  1, 19,  JOF_ATOM|JOF_NAME|JOF_TYPESET)
 OPDEF(JSOP_DOUBLE,    60, "double",     NULL,         5,  0,  1, 16,  JOF_DOUBLE)
 OPDEF(JSOP_STRING,    61, "string",     NULL,         5,  0,  1, 19,  JOF_ATOM)
 OPDEF(JSOP_ZERO,      62, "zero",       "0",          1,  0,  1, 16,  JOF_BYTE)