Bug 637985 - Reimplement watchpoints using a JSObject bit. r=jimb.
authorJason Orendorff <jorendorff@mozilla.com>
Wed, 27 Jul 2011 17:44:43 -0500
changeset 73454 7c43296e7545721e760bddc02c172b977ed752b0
parent 73453 cad13a541e30cf92a387dc584f08e5e4ea6d0f30
child 73455 45750d4529d95b4c9ed92589cfc1b7aeb0536653
push idunknown
push userunknown
push dateunknown
reviewersjimb
bugs637985
milestone8.0a1
Bug 637985 - Reimplement watchpoints using a JSObject bit. r=jimb.
js/src/Makefile.in
js/src/jit-test/tests/basic/bug510437.js
js/src/jit-test/tests/pic/bug645184.js
js/src/jit-test/tests/pic/watch1.js
js/src/jit-test/tests/pic/watch1a.js
js/src/jit-test/tests/pic/watch2.js
js/src/jit-test/tests/pic/watch2a.js
js/src/jit-test/tests/pic/watch3.js
js/src/jit-test/tests/pic/watch3a.js
js/src/jit-test/tests/pic/watch3b.js
js/src/jit-test/tests/pic/watch4.js
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsatominlines.h
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
js/src/jsdbgapiinlines.h
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jspropertycache.cpp
js/src/jsprvtd.h
js/src/jsscope.cpp
js/src/jsscopeinlines.h
js/src/jstracer.cpp
js/src/jstypedarray.cpp
js/src/jswatchpoint.cpp
js/src/jswatchpoint.h
js/src/jsxml.cpp
js/src/methodjit/MonoIC.cpp
js/src/methodjit/PolyIC.cpp
js/src/tests/js1_8_1/extensions/regress-452498-193.js
js/src/tests/js1_8_1/extensions/regress-452498-196.js
js/src/tests/js1_8_5/extensions/jstests.list
js/src/tests/js1_8_5/extensions/regress-637985.js
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -173,16 +173,17 @@ CPPSRCS		= \
 		jsreflect.cpp \
 		jsregexp.cpp \
 		jsscan.cpp \
 		jsscope.cpp \
 		jsscript.cpp \
 		jsstr.cpp \
 		jstypedarray.cpp \
 		jsutil.cpp \
+		jswatchpoint.cpp \
 		jsweakmap.cpp \
 		jswrapper.cpp \
 		jsxdrapi.cpp \
 		jsxml.cpp \
 		prmjtime.cpp \
 		sharkctl.cpp \
 		GlobalObject.cpp \
 		Stack.cpp \
--- a/js/src/jit-test/tests/basic/bug510437.js
+++ b/js/src/jit-test/tests/basic/bug510437.js
@@ -1,10 +1,11 @@
 // Don't crash or assert.
 
+var d;
 this.watch("d", eval);
 (function () {
     (eval("\
     (function () {\
         for (let x = 0; x < 2; ++x) {\
             d = x\
         }\
     })\
--- a/js/src/jit-test/tests/pic/bug645184.js
+++ b/js/src/jit-test/tests/pic/bug645184.js
@@ -1,8 +1,8 @@
-var obj = new Object();
-var passed = true;
-for (var i = 0; i < 100; i++) {
-if (obj['-1'] == null)
-    obj['-1'] = new Array();
-    assertEq(obj['-1'] == null, false);
-    obj = new Object();
-}
+var obj = new Object();
+var passed = true;
+for (var i = 0; i < 100; i++) {
+    if (obj['-1'] == null)
+        obj['-1'] = new Array();
+    assertEq(obj['-1'] == null, false);
+    obj = new Object();
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch1.js
@@ -0,0 +1,7 @@
+// assignments to watched objects must not be cached
+var obj = {x: 0};
+var hits = 0;
+obj.watch("x", function (id, oldval, newval) { hits++; return newval; });
+for (var i = 0; i < HOTLOOP + 2; i++)
+    obj.x = i;
+assertEq(hits, HOTLOOP + 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch1a.js
@@ -0,0 +1,17 @@
+// assignments to watched objects must not be traced
+var hits = 0;
+function counter(id, oldval, newval) {
+    hits++;
+    return newval;
+}
+
+(function () {
+    var obj = {x: 0, y: 0};
+    var a = ['x', 'y'];
+    obj.watch('z', counter);
+    for (var i = 0; i < HOTLOOP + 5 + 1; i++) {
+        obj.watch(a[+(i > HOTLOOP)], counter);
+        obj.y = i;
+    }
+})();
+assertEq(hits, 5);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch2.js
@@ -0,0 +1,8 @@
+// assignments to watched properties via ++ must not be cached
+var obj = {x: 0};
+var hits = 0;
+obj.watch("x", function (id, oldval, newval) { hits++; return newval; });
+for (var i = 0; i < HOTLOOP + 2; i++)
+    obj.x++;
+assertEq(hits, HOTLOOP + 2);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch2a.js
@@ -0,0 +1,18 @@
+// assignments to watched properties via ++ must not be traced
+var hits = 0;
+function counter(id, oldval, newval) {
+    hits++;
+    return newval;
+}
+
+(function () {
+    var obj = {x: 0, y: 0};
+    var a = ['x', 'y'];
+    obj.watch('z', counter);
+    for (var i = 0; i < HOTLOOP + 5 + 1; i++) {
+        obj.watch(a[+(i > HOTLOOP)], counter);
+        obj.y++;
+    }
+})();
+assertEq(hits, 5);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch3.js
@@ -0,0 +1,7 @@
+// assignment to watched global properties must not be cached
+x = 0;
+var hits = 0;
+this.watch("x", function (id, oldval, newval) { hits++; return newval; });
+for (var i = 0; i < HOTLOOP + 2; i++)
+    x = i;
+assertEq(hits, HOTLOOP + 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch3a.js
@@ -0,0 +1,19 @@
+// assignment to watched global properties must not be traced
+var hits = 0;
+function counter(id, oldval, newval) {
+    hits++;
+    return newval;
+}
+
+var x = 0;
+var y = 0;
+(function () {
+    var a = ['x', 'y'];
+    this.watch('z', counter);
+    for (var i = 0; i < HOTLOOP + 5 + 1; i++) {
+        this.watch(a[+(i > HOTLOOP)], counter);
+        y = 1;
+    }
+})();
+assertEq(hits, 5);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch3b.js
@@ -0,0 +1,20 @@
+// assignment to watched global properties must not be traced
+var hits = 0;
+function counter(id, oldval, newval) {
+    hits++;
+    return newval;
+}
+
+var x = 0;
+var y = 0;
+function f() {
+    var a = [{}, this];
+    for (var i = 0; i < HOTLOOP + 5 + 1; i++) {
+        print(shapeOf(this));
+        Object.prototype.watch.call(a[+(i > HOTLOOP)], "y", counter);
+        y++;
+    }
+}
+f();
+assertEq(hits, 5);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/pic/watch4.js
@@ -0,0 +1,9 @@
+// adding assignment + watchpoint vs. caching
+var hits = 0;
+var obj = {};
+obj.watch("x", function (id, oldval, newval) { hits++; return newval; });
+for (var i = 0; i < HOTLOOP + 2; i++) {
+    obj.x = 1;
+    delete obj.x;
+}
+assertEq(hits, HOTLOOP + 2);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -637,17 +637,16 @@ static JSBool js_NewRuntimeWasCalled = J
 
 JSRuntime::JSRuntime()
   : gcChunkAllocator(&defaultGCChunkAllocator),
     trustedPrincipals_(NULL)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&contextList);
     JS_INIT_CLIST(&trapList);
-    JS_INIT_CLIST(&watchPointList);
 }
 
 bool
 JSRuntime::init(uint32 maxbytes)
 {
 #ifdef JS_METHODJIT_SPEW
     JMCheckLogging();
 #endif
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -756,17 +756,17 @@ static JSBool
 array_setProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool strict)
 {
     uint32 i;
 
     if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom))
         return array_length_setter(cx, obj, id, strict, vp);
 
     if (!obj->isDenseArray())
-        return js_SetProperty(cx, obj, id, vp, strict);
+        return js_SetPropertyHelper(cx, obj, id, 0, vp, strict);
 
     do {
         if (!js_IdIsIndex(id, &i))
             break;
         if (js_PrototypeHasIndexedProperties(cx, obj))
             break;
 
         JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, i, 1);
@@ -780,17 +780,17 @@ array_setProperty(JSContext *cx, JSObjec
         if (i >= obj->getArrayLength())
             obj->setArrayLength(i + 1);
         obj->setDenseArrayElement(i, *vp);
         return true;
     } while (false);
 
     if (!obj->makeDenseArraySlow(cx))
         return false;
-    return js_SetProperty(cx, obj, id, vp, strict);
+    return js_SetPropertyHelper(cx, obj, id, 0, vp, strict);
 }
 
 JSBool
 js_PrototypeHasIndexedProperties(JSContext *cx, JSObject *obj)
 {
     /*
      * Walk up the prototype chain and see if this indexed element already
      * exists. If we hit the end of the prototype chain, it's safe to set the
--- a/js/src/jsatominlines.h
+++ b/js/src/jsatominlines.h
@@ -37,16 +37,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsatominlines_h___
 #define jsatominlines_h___
 
 #include "jsatom.h"
 #include "jsnum.h"
+#include "jsobj.h"
 
 inline bool
 js_ValueToAtom(JSContext *cx, const js::Value &v, JSAtom **atomp)
 {
     if (!v.isString()) {
         JSString *str = js_ValueToString(cx, v);
         if (!str)
             return false;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -516,17 +516,16 @@ struct JSRuntime {
     bool debuggerInhibitsJIT() const {
         return (globalDebugHooks.interruptHook ||
                 globalDebugHooks.callHook);
     }
 #endif
 
     /* More debugging state, see jsdbgapi.c. */
     JSCList             trapList;
-    JSCList             watchPointList;
 
     /* Client opaque pointers */
     void                *data;
 
 #ifdef JS_THREADSAFE
     /* These combine to interlock the GC and new requests. */
     PRLock              *gcLock;
     PRCondVar           *gcDone;
@@ -541,20 +540,19 @@ struct JSRuntime {
 #ifdef DEBUG
     void *              rtLockOwner;
 #endif
 
     /* Used to synchronize down/up state change; protected by gcLock. */
     PRCondVar           *stateChange;
 
     /*
-     * Lock serializing trapList and watchPointList accesses, and count of all
-     * mutations to trapList and watchPointList made by debugger threads.  To
-     * keep the code simple, we define debuggerMutations for the thread-unsafe
-     * case too.
+     * Lock serializing trapList accesses, and count of all mutations to
+     * trapList made by debugger threads. To keep the code simple, we define
+     * debuggerMutations for the thread-unsafe case too.
      */
     PRLock              *debuggerLock;
 
     JSThread::Map       threads;
 #endif /* JS_THREADSAFE */
     uint32              debuggerMutations;
 
     /*
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -42,16 +42,17 @@
 #include "jscompartment.h"
 #include "jsgc.h"
 #include "jsgcmark.h"
 #include "jsiter.h"
 #include "jsmath.h"
 #include "jsproxy.h"
 #include "jsscope.h"
 #include "jstracer.h"
+#include "jswatchpoint.h"
 #include "jswrapper.h"
 #include "assembler/wtf/Platform.h"
 #include "yarr/BumpPointerAllocator.h"
 #include "methodjit/MethodJIT.h"
 #include "methodjit/PolyIC.h"
 #include "methodjit/MonoIC.h"
 
 #include "jsgcinlines.h"
@@ -87,17 +88,18 @@ JSCompartment::JSCompartment(JSRuntime *
     emptyBlockShape(NULL),
     emptyCallShape(NULL),
     emptyDeclEnvShape(NULL),
     emptyEnumeratorShape(NULL),
     emptyWithShape(NULL),
     initialRegExpShape(NULL),
     initialStringShape(NULL),
     debugMode(rt->debugMode),
-    mathCache(NULL)
+    mathCache(NULL),
+    watchpointMap(NULL)
 {
     JS_INIT_CLIST(&scripts);
 
     PodArrayZero(scriptsToGC);
 }
 
 JSCompartment::~JSCompartment()
 {
@@ -109,16 +111,17 @@ JSCompartment::~JSCompartment()
     Foreground::delete_(jaegerCompartment_);
 #endif
 
 #ifdef JS_TRACER
     Foreground::delete_(traceMonitor_);
 #endif
 
     Foreground::delete_(mathCache);
+    Foreground::delete_(watchpointMap);
 
 #ifdef DEBUG
     for (size_t i = 0; i != JS_ARRAY_LENGTH(scriptsToGC); ++i)
         JS_ASSERT(!scriptsToGC[i]);
 #endif
 }
 
 bool
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -35,22 +35,22 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jscompartment_h___
 #define jscompartment_h___
 
+#include "jsclist.h"
 #include "jscntxt.h"
+#include "jsfun.h"
 #include "jsgc.h"
+#include "jsgcstats.h"
 #include "jsobj.h"
-#include "jsfun.h"
-#include "jsgcstats.h"
-#include "jsclist.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4251) /* Silence warning about JS_FRIEND_API and data members. */
 #endif
 
 namespace JSC { class ExecutableAllocator; }
 namespace WTF { class BumpPointerAllocator; }
@@ -555,16 +555,18 @@ struct JS_FRIEND_API(JSCompartment) {
     js::TraceMonitor *traceMonitor() const {
         JS_ASSERT(traceMonitor_);
         return traceMonitor_;
     }
 #endif
 
     size_t backEdgeCount(jsbytecode *pc) const;
     size_t incBackEdgeCount(jsbytecode *pc);
+
+    js::WatchpointMap *watchpointMap;
 };
 
 #define JS_SCRIPTS_TO_GC(cx)    ((cx)->compartment->scriptsToGC)
 #define JS_PROPERTY_TREE(cx)    ((cx)->compartment->propertyTree)
 
 /*
  * N.B. JS_ON_TRACE(cx) is true if JIT code is on the stack in the current
  * thread, regardless of whether cx is the context in which that trace is
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -60,20 +60,20 @@
 #include "jslock.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsparse.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstaticcheck.h"
 #include "jsstr.h"
+#include "jswatchpoint.h"
 #include "jswrapper.h"
 
 #include "jsatominlines.h"
-#include "jsdbgapiinlines.h"
 #include "jsobjinlines.h"
 #include "jsinterpinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
 
 #include "jsautooplen.h"
@@ -600,461 +600,23 @@ JS_ClearInterrupt(JSRuntime *rt, JSInter
 #ifdef JS_TRACER
     JITInhibitingHookChange(rt, wasInhibited);
 #endif
     return JS_TRUE;
 }
 
 /************************************************************************/
 
-struct JSWatchPoint {
-    JSCList             links;
-    JSObject            *object;        /* weak link, see js_SweepWatchPoints */
-    const Shape         *shape;
-    StrictPropertyOp    setter;
-    JSWatchPointHandler handler;
-    JSObject            *closure;
-    uintN               flags;
-};
-
-#define JSWP_LIVE       0x1             /* live because set and not cleared */
-#define JSWP_HELD       0x2             /* held while running handler/setter */
-
-/*
- * NB: DropWatchPointAndUnlock releases cx->runtime->debuggerLock in all cases.
- * The sweeping parameter is true if the watchpoint and its object are about to
- * be finalized, in which case we don't need to changeProperty.
- */
-static JSBool
-DropWatchPointAndUnlock(JSContext *cx, JSWatchPoint *wp, uintN flag, bool sweeping)
-{
-    bool ok = true;
-    JSRuntime *rt = cx->runtime;
-
-    wp->flags &= ~flag;
-    if (wp->flags != 0) {
-        DBG_UNLOCK(rt);
-        return ok;
-    }
-
-    /* Remove wp from the list, then restore wp->shape->setter from wp. */
-    ++rt->debuggerMutations;
-    JS_REMOVE_LINK(&wp->links);
-    DBG_UNLOCK(rt);
-
-    /*
-     * If the property isn't found on wp->object, then someone else must have deleted it,
-     * and we don't need to change the property attributes.
-     */
-    if (!sweeping) {
-        const Shape *shape = wp->shape;
-        const Shape *wprop = wp->object->nativeLookup(shape->propid);
-        if (wprop &&
-            wprop->hasSetterValue() == shape->hasSetterValue() &&
-            IsWatchedProperty(cx, wprop)) {
-            shape = wp->object->changeProperty(cx, wprop, 0, wprop->attributes(),
-                                               wprop->getter(), wp->setter);
-            if (!shape)
-                ok = false;
-        }
-    }
-
-    cx->free_(wp);
-    return ok;
-}
-
-/*
- * NB: js_TraceWatchPoints does not acquire cx->runtime->debuggerLock, since
- * the debugger should never be racing with the GC (i.e., the debugger must
- * respect the request model). If any unmarked objects were marked, this
- * function returns true and the GC will iteratively call this function again
- * until no more unmarked heap objects are found. This is necessary because
- * watch points have a weak pointer semantics.
- */
-JSBool
-js_TraceWatchPoints(JSTracer *trc)
-{
-    JSRuntime *rt;
-    JSWatchPoint *wp;
-
-    rt = trc->context->runtime;
-
-    bool modified = false;
-
-    for (wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = (JSWatchPoint *)wp->links.next) {
-        if (wp->object->isMarked()) {
-            if (!wp->shape->isMarked()) {
-                modified = true;
-                MarkShape(trc, wp->shape, "shape");
-            }
-            if (wp->shape->hasSetterValue() && wp->setter) {
-                if (!CastAsObject(wp->setter)->isMarked()) {
-                    modified = true;
-                    MarkObject(trc, *CastAsObject(wp->setter), "wp->setter");
-                }
-            }
-            if (!wp->closure->isMarked()) {
-                modified = true;
-                MarkObject(trc, *wp->closure, "wp->closure");
-            }
-        }
-    }
-
-    return modified;
-}
-
-void
-js_SweepWatchPoints(JSContext *cx)
-{
-    JSRuntime *rt;
-    JSWatchPoint *wp, *next;
-    uint32 sample;
-
-    rt = cx->runtime;
-    DBG_LOCK(rt);
-    for (wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = next) {
-        next = (JSWatchPoint *)wp->links.next;
-        if (IsAboutToBeFinalized(cx, wp->object)) {
-            sample = rt->debuggerMutations;
-
-            /* Ignore failures. */
-            DropWatchPointAndUnlock(cx, wp, JSWP_LIVE, true);
-            DBG_LOCK(rt);
-            if (rt->debuggerMutations != sample + 1)
-                next = (JSWatchPoint *)rt->watchPointList.next;
-        }
-    }
-    DBG_UNLOCK(rt);
-}
-
-
-
-/*
- * NB: LockedFindWatchPoint must be called with rt->debuggerLock acquired.
- */
-static JSWatchPoint *
-LockedFindWatchPoint(JSRuntime *rt, JSObject *obj, jsid propid)
-{
-    JSWatchPoint *wp;
-
-    for (wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = (JSWatchPoint *)wp->links.next) {
-        if (wp->object == obj && wp->shape->propid == propid)
-            return wp;
-    }
-    return NULL;
-}
-
-static JSWatchPoint *
-FindWatchPoint(JSRuntime *rt, JSObject *obj, jsid id)
-{
-    JSWatchPoint *wp;
-
-    DBG_LOCK(rt);
-    wp = LockedFindWatchPoint(rt, obj, id);
-    DBG_UNLOCK(rt);
-    return wp;
-}
-
-JSBool
-js_watch_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
-{
-    assertSameCompartment(cx, obj);
-    JSRuntime *rt = cx->runtime;
-    DBG_LOCK(rt);
-    for (JSWatchPoint *wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = (JSWatchPoint *)wp->links.next) {
-        const Shape *shape = wp->shape;
-        if (wp->object == obj && SHAPE_USERID(shape) == id && !(wp->flags & JSWP_HELD)) {
-            bool ok;
-            Value old;
-            uint32 slot;
-            const Shape *needMethodSlotWrite = NULL;
-
-            wp->flags |= JSWP_HELD;
-            DBG_UNLOCK(rt);
-
-            jsid propid = shape->propid;
-            shape = obj->nativeLookup(propid);
-            if (!shape) {
-                /*
-                 * This happens if the watched property has been deleted, but a
-                 * prototype has a watched accessor property with the same
-                 * name. See bug 636697.
-                 */
-                ok = true;
-                goto out;
-            }
-            JS_ASSERT(IsWatchedProperty(cx, shape));
-
-            /* Determine the property's old value. */
-            slot = shape->slot;
-            old = obj->containsSlot(slot) ? obj->nativeGetSlot(slot) : UndefinedValue();
-            if (shape->isMethod()) {
-                /*
-                 * We get here in two cases: (1) the existing watched property
-                 * is a method; or (2) the watched property was deleted and is
-                 * now in the middle of being re-added via JSOP_SETMETHOD. In
-                 * both cases we must trip the method read barrier in order to
-                 * avoid passing an uncloned function object to the handler.
-                 *
-                 * Case 2 is especially hairy. js_watch_set, uniquely, gets
-                 * called in the middle of creating a method property, after
-                 * shape is in obj but before the slot has been set. So in this
-                 * case we must finish initializing the half-finished method
-                 * property before triggering the method read barrier.
-                 *
-                 * Bonus weirdness: because this changes obj's shape,
-                 * js_NativeSet (which is our caller) will not write to the
-                 * slot, as it will appear the property was deleted and a new
-                 * property added. We must write the slot ourselves -- however
-                 * we must do it after calling the watchpoint handler. So set
-                 * needMethodSlotWrite here and use it to write to the slot
-                 * below, if the handler does not tinker with the property
-                 * further.
-                 */
-                JS_ASSERT(!wp->setter);
-                Value method = ObjectValue(shape->methodObject());
-                if (old.isUndefined())
-                    obj->nativeSetSlot(slot, method);
-                ok = obj->methodReadBarrier(cx, *shape, &method);
-                if (!ok)
-                    goto out;
-                wp->shape = shape = needMethodSlotWrite = obj->nativeLookup(propid);
-                JS_ASSERT(shape->isDataDescriptor());
-                JS_ASSERT(!shape->isMethod());
-                if (old.isUndefined())
-                    obj->nativeSetSlot(shape->slot, old);
-                else
-                    old = method;
-            }
-
-            {
-                Maybe<AutoShapeRooter> tvr;
-                if (needMethodSlotWrite)
-                    tvr.construct(cx, needMethodSlotWrite);
-
-                /*
-                 * Call the handler. This invalidates shape, so re-lookup the shape.
-                 * NB: wp is held, so we can safely dereference it still.
-                 */
-                ok = wp->handler(cx, obj, propid, Jsvalify(old), Jsvalify(vp), wp->closure);
-                if (!ok)
-                    goto out;
-                shape = obj->nativeLookup(propid);
-
-                if (!shape) {
-                    ok = true;
-                } else if (wp->setter) {
-                    /*
-                     * Pass the output of the handler to the setter. Security wrappers
-                     * prevent any funny business between watchpoints and setters.
-                     */
-                    ok = shape->hasSetterValue()
-                         ? ExternalInvoke(cx, ObjectValue(*obj),
-                                          ObjectValue(*CastAsObject(wp->setter)),
-                                          1, vp, vp)
-                         : CallJSPropertyOpSetter(cx, wp->setter, obj, SHAPE_USERID(shape),
-                                                  strict, vp);
-                } else if (shape == needMethodSlotWrite) {
-                    /* See comment above about needMethodSlotWrite. */
-                    obj->nativeSetSlot(shape->slot, *vp);
-                    ok = true;
-                } else {
-                    /*
-                     * A property with the default setter might be either a method
-                     * or an ordinary function-valued data property subject to the
-                     * method write barrier.
-                     *
-                     * It is not the setter's job to call methodWriteBarrier,
-                     * but js_watch_set must do so, because the caller will be
-                     * fooled into not doing it: shape does *not* have the
-                     * default setter and therefore seems not to be a method.
-                     */
-                    ok = obj->methodWriteBarrier(cx, *shape, *vp) != NULL;
-                }
-            }
-
-        out:
-            DBG_LOCK(rt);
-            return DropWatchPointAndUnlock(cx, wp, JSWP_HELD, false) && ok;
-        }
-    }
-    DBG_UNLOCK(rt);
-    return true;
-}
-
-static JSBool
-js_watch_set_wrapper(JSContext *cx, uintN argc, Value *vp)
-{
-    JSObject *obj = ToObject(cx, &vp[1]);
-    if (!obj)
-        return false;
-
-    JSObject &funobj = JS_CALLEE(cx, vp).toObject();
-    JSFunction *wrapper = funobj.getFunctionPrivate();
-    jsid userid = ATOM_TO_JSID(wrapper->atom);
-
-    JS_SET_RVAL(cx, vp, argc ? JS_ARGV(cx, vp)[0] : UndefinedValue());
-    /*
-     * The strictness we pass here doesn't matter, since we know that it's
-     * a JS setter, which can't depend on the assigning code's strictness.
-     */
-    return js_watch_set(cx, obj, userid, false, vp);
-}
-
-namespace js {
-
-bool
-IsWatchedProperty(JSContext *cx, const Shape *shape)
-{
-    if (shape->hasSetterValue()) {
-        JSObject *funobj = shape->setterObject();
-        if (!funobj || !funobj->isFunction())
-            return false;
-
-        JSFunction *fun = funobj->getFunctionPrivate();
-        return fun->maybeNative() == js_watch_set_wrapper;
-    }
-    return shape->setterOp() == js_watch_set;
-}
-
-}
-
-/*
- * Return an appropriate setter to substitute for |setter| on a property
- * with attributes |attrs|, to implement a watchpoint on the property named
- * |id|.
- */
-static StrictPropertyOp
-WrapWatchedSetter(JSContext *cx, jsid id, uintN attrs, StrictPropertyOp setter)
-{
-    JSAtom *atom;
-    JSFunction *wrapper;
-
-    /* Wrap a C++ setter simply by returning our own C++ setter. */
-    if (!(attrs & JSPROP_SETTER))
-        return &js_watch_set;   /* & to silence schoolmarmish MSVC */
-
-    /*
-     * Wrap a JSObject * setter by constructing our own JSFunction * that saves the
-     * property id as the function name, and calls js_watch_set.
-     */
-    if (JSID_IS_ATOM(id)) {
-        atom = JSID_TO_ATOM(id);
-    } else if (JSID_IS_INT(id)) {
-        if (!js_ValueToStringId(cx, IdToValue(id), &id))
-            return NULL;
-        atom = JSID_TO_ATOM(id);
-    } else {
-        atom = NULL;
-    }
-
-    wrapper = js_NewFunction(cx, NULL, js_watch_set_wrapper, 1, 0,
-                             setter ? CastAsObject(setter)->getParent() : NULL, atom);
-    if (!wrapper)
-        return NULL;
-    return CastAsStrictPropertyOp(FUN_OBJECT(wrapper));
-}
-
-static const Shape *
-UpdateWatchpointShape(JSContext *cx, JSWatchPoint *wp, const Shape *newShape)
-{
-    JS_ASSERT_IF(wp->shape, wp->shape->propid == newShape->propid);
-    JS_ASSERT(!IsWatchedProperty(cx, newShape));
-
-    /* Create a watching setter we can substitute for the new shape's setter. */
-    StrictPropertyOp watchingSetter =
-        WrapWatchedSetter(cx, newShape->propid, newShape->attributes(), newShape->setter());
-    if (!watchingSetter)
-        return NULL;
-
-    /*
-     * Save the shape's setter; we don't know whether js_ChangeNativePropertyAttrs will
-     * return a new shape, or mutate this one.
-     */
-    StrictPropertyOp originalSetter = newShape->setter();
-
-    /*
-     * Drop the watching setter into the object, in place of newShape. Note that a single
-     * watchpoint-wrapped shape may correspond to more than one non-watchpoint shape: we
-     * wrap all (JSPropertyOp, not JSObject *) setters with js_watch_set, so shapes that
-     * differ only in their setter may all get wrapped to the same shape.
-     */
-    const Shape *watchingShape = 
-        js_ChangeNativePropertyAttrs(cx, wp->object, newShape, 0, newShape->attributes(),
-                                     newShape->getter(), watchingSetter);
-    if (!watchingShape)
-        return NULL;
-
-    /* Update the watchpoint with the new shape and its original setter. */
-    wp->setter = originalSetter;
-    wp->shape = watchingShape;
-
-    return watchingShape;
-}
-
-const Shape *
-js_SlowPathUpdateWatchpointsForShape(JSContext *cx, JSObject *obj, const Shape *newShape)
-{
-    assertSameCompartment(cx, obj);
-
-    /*
-     * The watchpoint code uses the normal property-modification functions to install its
-     * own watchpoint-aware shapes. Those functions report those changes back to the
-     * watchpoint code, just as they do user-level changes. So if this change is
-     * installing a watchpoint-aware shape, it's something we asked for ourselves, and can
-     * proceed without interference.
-     */
-    if (IsWatchedProperty(cx, newShape))
-        return newShape;
-
-    JSWatchPoint *wp = FindWatchPoint(cx->runtime, obj, newShape->propid);
-    if (!wp)
-        return newShape;
-
-    return UpdateWatchpointShape(cx, wp, newShape);
-}
-
-/*
- * Return the underlying setter for |shape| on |obj|, seeing through any
- * watchpoint-wrapping. Note that we need |obj| to disambiguate, since a single
- * watchpoint-wrapped shape may correspond to more than one non-watchpoint shape; see the
- * comments in UpdateWatchpointShape.
- */
-static StrictPropertyOp
-UnwrapSetter(JSContext *cx, JSObject *obj, const Shape *shape)
-{
-    /* If it's not a watched property, its setter is not wrapped. */
-    if (!IsWatchedProperty(cx, shape))
-        return shape->setter();
-
-    /* Look up the watchpoint, from which we can retrieve the underlying setter. */
-    JSWatchPoint *wp = FindWatchPoint(cx->runtime, obj, shape->propid);
-
-    /* 
-     * Since we know |shape| is watched, we *must* find a watchpoint: we should never
-     * leave wrapped setters lying around in shapes after removing a watchpoint.
-     */
-    JS_ASSERT(wp);
-
-    return wp->setter;
-}
 
 JS_PUBLIC_API(JSBool)
 JS_SetWatchPoint(JSContext *cx, JSObject *obj, jsid id,
                  JSWatchPointHandler handler, JSObject *closure)
 {
     assertSameCompartment(cx, obj);
+    id = js_CheckForStringIndex(id);
 
     JSObject *origobj;
     Value v;
     uintN attrs;
     jsid propid;
 
     origobj = obj;
     OBJ_TO_INNER_OBJECT(cx, obj);
@@ -1079,205 +641,58 @@ JS_SetWatchPoint(JSContext *cx, JSObject
         return false;
 
     if (!obj->isNative()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_WATCH,
                              obj->getClass()->name);
         return false;
     }
 
-    JSObject *pobj;
-    JSProperty *prop;
-    if (!js_LookupProperty(cx, obj, propid, &pobj, &prop))
-        return false;
-    const Shape *shape = (Shape *) prop;
-    JSRuntime *rt = cx->runtime;
-    if (!shape) {
-        /* Check for a deleted symbol watchpoint, which holds its property. */
-        JSWatchPoint *wp = FindWatchPoint(rt, obj, propid);
-        if (!wp) {
-            /* Make a new property in obj so we can watch for the first set. */
-            shape = DefineNativeProperty(cx, obj, propid, UndefinedValue(), NULL, NULL,
-                                         JSPROP_ENUMERATE, 0, 0);
-            if (!shape)
-                return false;
-        }
-    } else if (pobj != obj) {
-        /* Clone the prototype property so we can watch the right object. */
-        AutoValueRooter valroot(cx);
-        PropertyOp getter;
-        StrictPropertyOp setter;
-        uintN attrs, flags;
-        intN shortid;
-
-        if (pobj->isNative()) {
-            if (shape->isMethod()) {
-                Value method = ObjectValue(shape->methodObject());
-                shape = pobj->methodReadBarrier(cx, *shape, &method);
-                if (!shape)
-                    return false;
-            }
-
-            valroot.set(pobj->containsSlot(shape->slot)
-                        ? pobj->nativeGetSlot(shape->slot)
-                        : UndefinedValue());
-            getter = shape->getter();
-            setter = UnwrapSetter(cx, pobj, shape);
-            attrs = shape->attributes();
-            flags = shape->getFlags();
-            shortid = shape->shortid;
-        } else {
-            if (!pobj->getProperty(cx, propid, valroot.addr()) ||
-                !pobj->getAttributes(cx, propid, &attrs)) {
-                return false;
-            }
-            getter = NULL;
-            setter = NULL;
-            flags = 0;
-            shortid = 0;
-        }
-
-        /* Recall that obj is native, whether or not pobj is native. */
-        shape = DefineNativeProperty(cx, obj, propid, valroot.value(), getter, setter,
-                                     attrs, flags, shortid);
-        if (!shape)
-            return false;
-    }
-
-    /*
-     * At this point, prop/shape exists in obj, obj is locked, and we must
-     * unlock the object before returning.
-     */
-    DBG_LOCK(rt);
-    JSWatchPoint *wp = LockedFindWatchPoint(rt, obj, propid);
-    if (!wp) {
-        DBG_UNLOCK(rt);
-        wp = (JSWatchPoint *) cx->malloc_(sizeof *wp);
-        if (!wp)
-            return false;
-        wp->handler = NULL;
-        wp->closure = NULL;
-        wp->object = obj;
-        wp->shape = NULL;
-        wp->flags = JSWP_LIVE;
-
-        /* XXXbe nest in obj lock here */
-        if (!UpdateWatchpointShape(cx, wp, shape)) {
-            /* Self-link so DropWatchPointAndUnlock can JS_REMOVE_LINK it. */
-            JS_INIT_CLIST(&wp->links);
-            DBG_LOCK(rt);
-            DropWatchPointAndUnlock(cx, wp, JSWP_LIVE, false);
+    WatchpointMap *wpmap = cx->compartment->watchpointMap;
+    if (!wpmap) {
+        wpmap = cx->runtime->new_<WatchpointMap>();
+        if (!wpmap || !wpmap->init()) {
+            js_ReportOutOfMemory(cx);
             return false;
         }
-
-        /*
-         * Now that wp is fully initialized, append it to rt's wp list.
-         * Because obj is locked we know that no other thread could have added
-         * a watchpoint for (obj, propid).
-         */
-        DBG_LOCK(rt);
-        JS_ASSERT(!LockedFindWatchPoint(rt, obj, propid));
-        JS_APPEND_LINK(&wp->links, &rt->watchPointList);
-        ++rt->debuggerMutations;
+        cx->compartment->watchpointMap = wpmap;
     }
-
-    /*
-     * Ensure that an object with watchpoints never has the same shape as an
-     * object without them, even if the watched properties are deleted.
-     */
-    obj->watchpointOwnShapeChange(cx);
-
-    wp->handler = handler;
-    wp->closure = reinterpret_cast<JSObject*>(closure);
-    DBG_UNLOCK(rt);
-    return true;
+    return wpmap->watch(cx, obj, id, handler, closure);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ClearWatchPoint(JSContext *cx, JSObject *obj, jsid id,
                    JSWatchPointHandler *handlerp, JSObject **closurep)
 {
-    assertSameCompartment(cx, obj);
-
-    JSRuntime *rt;
-    JSWatchPoint *wp;
+    assertSameCompartment(cx, obj, id);
 
-    rt = cx->runtime;
-    DBG_LOCK(rt);
-    for (wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = (JSWatchPoint *)wp->links.next) {
-        if (wp->object == obj && SHAPE_USERID(wp->shape) == id) {
-            if (handlerp)
-                *handlerp = wp->handler;
-            if (closurep)
-                *closurep = wp->closure;
-            return DropWatchPointAndUnlock(cx, wp, JSWP_LIVE, false);
-        }
-    }
-    DBG_UNLOCK(rt);
-    if (handlerp)
-        *handlerp = NULL;
-    if (closurep)
-        *closurep = NULL;
-    return JS_TRUE;
+    id = js_CheckForStringIndex(id);
+    if (WatchpointMap *wpmap = cx->compartment->watchpointMap)
+        wpmap->unwatch(obj, id, handlerp, closurep);
+    return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ClearWatchPointsForObject(JSContext *cx, JSObject *obj)
 {
     assertSameCompartment(cx, obj);
 
-    JSRuntime *rt;
-    JSWatchPoint *wp, *next;
-    uint32 sample;
-
-    rt = cx->runtime;
-    DBG_LOCK(rt);
-    for (wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = next) {
-        next = (JSWatchPoint *)wp->links.next;
-        if (wp->object == obj) {
-            sample = rt->debuggerMutations;
-            if (!DropWatchPointAndUnlock(cx, wp, JSWP_LIVE, false))
-                return JS_FALSE;
-            DBG_LOCK(rt);
-            if (rt->debuggerMutations != sample + 1)
-                next = (JSWatchPoint *)rt->watchPointList.next;
-        }
-    }
-    DBG_UNLOCK(rt);
-    return JS_TRUE;
+    if (WatchpointMap *wpmap = cx->compartment->watchpointMap)
+        wpmap->unwatchObject(obj);
+    return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ClearAllWatchPoints(JSContext *cx)
 {
-    JSRuntime *rt;
-    JSWatchPoint *wp, *next;
-    uint32 sample;
-
-    rt = cx->runtime;
-    DBG_LOCK(rt);
-    for (wp = (JSWatchPoint *)rt->watchPointList.next;
-         &wp->links != &rt->watchPointList;
-         wp = next) {
-        SwitchToCompartment sc(cx, wp->object);
-
-        next = (JSWatchPoint *)wp->links.next;
-        sample = rt->debuggerMutations;
-        if (!DropWatchPointAndUnlock(cx, wp, JSWP_LIVE, false))
-            return JS_FALSE;
-        DBG_LOCK(rt);
-        if (rt->debuggerMutations != sample + 1)
-            next = (JSWatchPoint *)rt->watchPointList.next;
+    if (JSCompartment *comp = cx->compartment) {
+        if (WatchpointMap *wpmap = comp->watchpointMap)
+            wpmap->clear();
     }
-    DBG_UNLOCK(rt);
-    return JS_TRUE;
+    return true;
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(uintN)
 JS_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
     return js_PCToLineNumber(cx, script, pc);
--- a/js/src/jsdbgapi.h
+++ b/js/src/jsdbgapi.h
@@ -184,43 +184,16 @@ JS_ClearWatchPoint(JSContext *cx, JSObje
                    JSWatchPointHandler *handlerp, JSObject **closurep);
 
 extern JS_PUBLIC_API(JSBool)
 JS_ClearWatchPointsForObject(JSContext *cx, JSObject *obj);
 
 extern JS_PUBLIC_API(JSBool)
 JS_ClearAllWatchPoints(JSContext *cx);
 
-#ifdef JS_HAS_OBJ_WATCHPOINT
-/*
- * Hide these non-API function prototypes by testing whether the internal
- * header file "jsversion.h" has been included.
- */
-extern JSBool
-js_TraceWatchPoints(JSTracer *trc);
-
-extern void
-js_SweepWatchPoints(JSContext *cx);
-
-#ifdef __cplusplus
-
-extern JSBool
-js_watch_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, js::Value *vp);
-
-namespace js {
-
-bool
-IsWatchedProperty(JSContext *cx, const Shape *shape);
-
-}
-
-#endif
-
-#endif /* JS_HAS_OBJ_WATCHPOINT */
-
 /************************************************************************/
 
 extern JS_PUBLIC_API(uintN)
 JS_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc);
 
 extern JS_PUBLIC_API(jsbytecode *)
 JS_LineNumberToPC(JSContext *cx, JSScript *script, uintN lineno);
 
deleted file mode 100644
--- a/js/src/jsdbgapiinlines.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=4 sw=4 et tw=78:
- *
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is SpiderMonkey code.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Jim Blandy <jimb@mozilla.com> (original author)
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef jsdbgapiinlines_h___
-#define jsdbgapiinlines_h___
-
-#include "jsdbgapi.h"
-#include "jscntxt.h"
-
-#if defined(JS_HAS_OBJ_WATCHPOINT) && defined(__cplusplus)
-
-extern const js::Shape *
-js_SlowPathUpdateWatchpointsForShape(JSContext *cx, JSObject *obj, const js::Shape *newShape);
-
-/*
- * Update any watchpoints on |obj| on |newShape->id| to use |newShape|. Property-manipulating
- * functions must call this any time it takes on a new shape to represent a potentially
- * watched property, or when it mutates a shape's attributes/setter/getter.
- */
-static inline const js::Shape *
-js_UpdateWatchpointsForShape(JSContext *cx, JSObject *obj, const js::Shape *newShape)
-{
-    if (JS_CLIST_IS_EMPTY(&cx->runtime->watchPointList))
-        return newShape;
-
-    return js_SlowPathUpdateWatchpointsForShape(cx, obj, newShape);
-}
-
-#endif /* JS_HAS_OBJ_WATCHPOINT && __cplusplus */
-
-#endif /* jsdbgapiinlines_h__ */
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -524,17 +524,16 @@ StrictArgGetter(JSContext *cx, JSObject 
         JS_ASSERT(JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom));
         if (!argsobj->hasOverriddenLength())
             vp->setInt32(argsobj->initialLength());
     }
     return true;
 }
 
 static JSBool
-
 StrictArgSetter(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
     if (!obj->isStrictArguments())
         return true;
 
     StrictArgumentsObject *argsobj = obj->asStrictArguments();
 
     if (JSID_IS_INT(id)) {
@@ -550,17 +549,17 @@ StrictArgSetter(JSContext *cx, JSObject 
     /*
      * For simplicity we use delete/set to replace the property with one
      * backed by the default Object getter and setter. Note that we rely on
      * args_delProperty to clear the corresponding reserved slot so the GC can
      * collect its value.
      */
     AutoValueRooter tvr(cx);
     return js_DeleteProperty(cx, argsobj, id, tvr.addr(), strict) &&
-           js_SetProperty(cx, argsobj, id, vp, strict);
+           js_SetPropertyHelper(cx, argsobj, id, 0, vp, strict);
 }
 
 static JSBool
 strictargs_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSObject **objp)
 {
     *objp = NULL;
 
     StrictArgumentsObject *argsobj = obj->asStrictArguments();
@@ -1291,22 +1290,16 @@ StackFrame::getValidCalleeObject(JSConte
     }
 
     return true;
 }
 
 static JSBool
 fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
-    /*
-     * Note how that clobbering is what simulates JSPROP_READONLY for all of
-     * the non-standard properties when the directly addressed object (obj)
-     * is a function object (i.e., when this loop does not iterate).
-     */
-
     while (!obj->isFunction()) {
         obj = obj->getProto();
         if (!obj)
             return true;
     }
     JSFunction *fun = obj->getFunctionPrivate();
 
     /* Set to early to null in case of error */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -77,16 +77,17 @@
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsparse.h"
 #include "jsprobes.h"
 #include "jsproxy.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstaticcheck.h"
+#include "jswatchpoint.h"
 #include "jsweakmap.h"
 #if JS_HAS_XML_SUPPORT
 #include "jsxml.h"
 #endif
 
 #include "methodjit/MethodJIT.h"
 #include "vm/String.h"
 
@@ -2271,17 +2272,17 @@ MarkAndSweep(JSContext *cx, JSCompartmen
     MarkRuntime(&gcmarker);
 
     gcmarker.drainMarkStack();
 
     /*
      * Mark weak roots.
      */
     while (true) {
-        if (!js_TraceWatchPoints(&gcmarker) && !WeakMapBase::markAllIteratively(&gcmarker))
+        if (!WatchpointMap::markAllIteratively(&gcmarker) && !WeakMapBase::markAllIteratively(&gcmarker))
             break;
         gcmarker.drainMarkStack();
     }
 
     rt->gcMarkingTracer = NULL;
 
     if (rt->gcCallback)
         (void) rt->gcCallback(cx, JSGC_MARK_END);
@@ -2310,21 +2311,21 @@ MarkAndSweep(JSContext *cx, JSCompartmen
      */
     GCTIMESTAMP(startSweep);
 
     /* Finalize unreachable (key,value) pairs in all weak maps. */
     WeakMapBase::sweepAll(&gcmarker);
 
     js_SweepAtomState(cx);
 
-    /* Finalize watch points associated with unreachable objects. */
-    js_SweepWatchPoints(cx);
+    /* Collect watch points associated with unreachable objects. */
+    WatchpointMap::sweepAll(rt);
 
     /*
-     * We finalize objects before other GC things to ensure that object's finalizer
+     * We finalize objects before other GC things to ensure that object's finalizer 
      * can access them even if they will be freed. Sweep the runtime's property trees
      * after finalizing objects, in case any had watchpoints referencing tree nodes.
      * Do this before sweeping compartments, so that we sweep all shapes in
      * unreachable compartments.
      */
     if (comp) {
         comp->sweep(cx, 0);
         comp->finalizeObjectArenaLists(cx);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -75,16 +75,17 @@
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstaticcheck.h"
 #include "jsstdint.h"
 #include "jsstr.h"
 #include "jstracer.h"
 #include "jsdbgapi.h"
 #include "json.h"
+#include "jswatchpoint.h"
 #include "jswrapper.h"
 
 #include "jsinterpinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/StringObject-inl.h"
@@ -3328,16 +3329,28 @@ JSObject::defineBlockVariable(JSContext 
                                      Shape::HAS_SHORTID, index);
     if (!shape)
         return NULL;
     if (slot >= numSlots() && !growSlots(cx, slot + 1))
         return NULL;
     return shape;
 }
 
+JSBool
+JSObject::nonNativeSetProperty(JSContext *cx, jsid id, js::Value *vp, JSBool strict)
+{
+    if (JS_UNLIKELY(watched())) {
+        id = js_CheckForStringIndex(id);
+        WatchpointMap *wpmap = cx->compartment->watchpointMap;
+        if (wpmap && !wpmap->triggerWatchpoint(cx, this, id, vp))
+            return false;
+    }
+    return getOps()->setProperty(cx, this, id, vp, strict);
+}
+
 static size_t
 GetObjectSize(JSObject *obj)
 {
     return (obj->isFunction() && !obj->getPrivate())
            ? sizeof(JSFunction)
            : sizeof(JSObject) + sizeof(js::Value) * obj->numFixedSlots();
 }
 
@@ -5393,16 +5406,26 @@ js_SetPropertyHelper(JSContext *cx, JSOb
 
     JS_ASSERT((defineHow & ~(DNP_CACHE_RESULT | DNP_SET_METHOD | DNP_UNQUALIFIED)) == 0);
     if (defineHow & DNP_CACHE_RESULT)
         JS_ASSERT_NOT_ON_TRACE(cx);
 
     /* Convert string indices to integers if appropriate. */
     id = js_CheckForStringIndex(id);
 
+    if (JS_UNLIKELY(obj->watched())) {
+        /* Fire watchpoints, if any. */
+        WatchpointMap *wpmap = cx->compartment->watchpointMap;
+        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
+            return false;
+
+        /* A watchpoint handler may set *vp to a non-function value. */
+        defineHow &= ~DNP_SET_METHOD;
+    }
+
     if (!LookupPropertyWithFlags(cx, obj, id, cx->resolveFlags, &pobj, &prop))
         return false;
     if (prop) {
         if (!pobj->isNative()) {
             if (pobj->isProxy()) {
                 AutoPropertyDescriptorRooter pd(cx);
                 if (!JSProxy::getPropertyDescriptor(cx, pobj, id, true, &pd))
                     return false;
@@ -5615,22 +5638,16 @@ js_SetPropertyHelper(JSContext *cx, JSOb
 
 #ifdef JS_TRACER
   error: // TRACE_1 jumps here in case of error.
     return JS_FALSE;
 #endif
 }
 
 JSBool
-js_SetProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool strict)
-{
-    return js_SetPropertyHelper(cx, obj, id, 0, vp, strict);
-}
-
-JSBool
 js_GetAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
 {
     JSProperty *prop;
     if (!js_LookupProperty(cx, obj, id, &obj, &prop))
         return false;
     if (!prop) {
         *attrsp = 0;
         return true;
@@ -6487,18 +6504,16 @@ DumpProperty(JSObject *obj, const Shape 
 
     if (shape.hasGetterValue())
         fprintf(stderr, "getterValue=%p ", (void *) shape.getterObject());
     else if (!shape.hasDefaultGetter())
         fprintf(stderr, "getterOp=%p ", JS_FUNC_TO_DATA_PTR(void *, shape.getterOp()));
 
     if (shape.hasSetterValue())
         fprintf(stderr, "setterValue=%p ", (void *) shape.setterObject());
-    else if (shape.setterOp() == js_watch_set)
-        fprintf(stderr, "setterOp=js_watch_set ");
     else if (!shape.hasDefaultSetter())
         fprintf(stderr, "setterOp=%p ", JS_FUNC_TO_DATA_PTR(void *, shape.setterOp()));
 
     if (JSID_IS_ATOM(id))
         dumpString(JSID_TO_STRING(id));
     else if (JSID_IS_INT(id))
         fprintf(stderr, "%d", (int) JSID_TO_INT(id));
     else
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -230,17 +230,18 @@ js_GetProperty(JSContext *cx, JSObject *
 namespace js {
 
 extern JSBool
 GetPropertyDefault(JSContext *cx, JSObject *obj, jsid id, const Value &def, Value *vp);
 
 } /* namespace js */
 
 extern JSBool
-js_SetProperty(JSContext *cx, JSObject *obj, jsid id, js::Value *vp, JSBool strict);
+js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
+                     js::Value *vp, JSBool strict);
 
 extern JSBool
 js_GetAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp);
 
 extern JSBool
 js_SetAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp);
 
 extern JSBool
@@ -364,18 +365,19 @@ struct JSObject : js::gc::Cell {
         INDEXED                   =       0x40,
         OWN_SHAPE                 =       0x80,
         METHOD_THRASH_COUNT_MASK  =      0x300,
         METHOD_THRASH_COUNT_SHIFT =          8,
         METHOD_THRASH_COUNT_MAX   = METHOD_THRASH_COUNT_MASK >> METHOD_THRASH_COUNT_SHIFT,
         BOUND_FUNCTION            =      0x400,
         HAS_EQUALITY              =      0x800,
         VAROBJ                    =     0x1000,
+        WATCHED                   =     0x2000,
 
-        UNUSED_FLAG_BITS          = 0xFFFFE000
+        UNUSED_FLAG_BITS          = 0xFFFFC000
     };
 
     /*
      * Impose a sane upper bound, originally checked only for dense arrays, on
      * number of slots in an object.
      */
     enum {
         NSLOTS_BITS     = 29,
@@ -475,20 +477,28 @@ struct JSObject : js::gc::Cell {
     bool hasSpecialEquality() const { return !!(flags & HAS_EQUALITY); }
     void assertSpecialEqualitySynced() const {
         JS_ASSERT(!!clasp->ext.equality == hasSpecialEquality());
     }
 
     /* Sets an object's HAS_EQUALITY flag based on its clasp. */
     inline void syncSpecialEquality();
 
-    /* See StackFrame::varObj. */
-    inline bool isVarObj() const { return flags & VAROBJ; }
-    inline void makeVarObj() { flags |= VAROBJ; }
+    bool watched() const { return !!(flags & WATCHED); }
 
+    void setWatched(JSContext *cx) {
+        if (!watched()) {
+            flags |= WATCHED;
+            generateOwnShape(cx);
+        }
+    }
+
+   /* See StackFrame::varObj. */
+   inline bool isVarObj() const { return flags & VAROBJ; }
+   inline void makeVarObj() { flags |= VAROBJ; }
   private:
     void generateOwnShape(JSContext *cx);
 
     inline void setOwnShape(uint32 s);
     inline void clearOwnShape();
 
   public:
     inline bool nativeEmpty() const;
@@ -505,17 +515,16 @@ struct JSObject : js::gc::Cell {
     void setBlockOwnShape(JSContext *cx);
 
     void deletingShapeChange(JSContext *cx, const js::Shape &shape);
     const js::Shape *methodShapeChange(JSContext *cx, const js::Shape &shape);
     bool methodShapeChange(JSContext *cx, uint32 slot);
     void protoShapeChange(JSContext *cx);
     void shadowingShapeChange(JSContext *cx, const js::Shape &shape);
     bool globalObjectOwnShapeChange(JSContext *cx);
-    void watchpointOwnShapeChange(JSContext *cx) { generateOwnShape(cx); }
 
     void extensibleShapeChange(JSContext *cx) {
         /* This will do for now. */
         generateOwnShape(cx);
     }
 
     /*
      * A scope has a method barrier when some compiler-created "null closure"
@@ -1174,20 +1183,23 @@ struct JSObject : js::gc::Cell {
         return (op ? op : (js::PropertyIdOp)js_GetProperty)(cx, this, receiver, id, vp);
     }
 
     JSBool getProperty(JSContext *cx, jsid id, js::Value *vp) {
         return getProperty(cx, this, id, vp);
     }
 
     JSBool setProperty(JSContext *cx, jsid id, js::Value *vp, JSBool strict) {
-        js::StrictPropertyIdOp op = getOps()->setProperty;
-        return (op ? op : js_SetProperty)(cx, this, id, vp, strict);
+        if (getOps()->setProperty)
+            return nonNativeSetProperty(cx, id, vp, strict);
+        return js_SetPropertyHelper(cx, this, id, 0, vp, strict);
     }
 
+    JSBool nonNativeSetProperty(JSContext *cx, jsid id, js::Value *vp, JSBool strict);
+
     JSBool getAttributes(JSContext *cx, jsid id, uintN *attrsp) {
         js::AttributesOp op = getOps()->getAttributes;
         return (op ? op : js_GetAttributes)(cx, this, id, attrsp);
     }
 
     JSBool setAttributes(JSContext *cx, jsid id, uintN *attrsp) {
         js::AttributesOp op = getOps()->setAttributes;
         return (op ? op : js_SetAttributes)(cx, this, id, attrsp);
@@ -1743,20 +1755,16 @@ js_GetMethod(JSContext *cx, JSObject *ob
  * Check whether it is OK to assign an undeclared property with name
  * propname of the global object in the current script on cx.  Reports
  * an error if one needs to be reported (in particular in all cases
  * when it returns false).
  */
 extern JS_FRIEND_API(bool)
 js_CheckUndeclaredVarAssignment(JSContext *cx, JSString *propname);
 
-extern JSBool
-js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
-                     js::Value *vp, JSBool strict);
-
 /*
  * Change attributes for the given native property. The caller must ensure
  * that obj is locked and this function always unlocks obj on return.
  */
 extern JSBool
 js_SetNativeAttributes(JSContext *cx, JSObject *obj, js::Shape *shape,
                        uintN attrs);
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -207,17 +207,17 @@ JSObject::methodReadBarrier(JSContext *c
 {
     JS_ASSERT(canHaveMethodBarrier());
     JS_ASSERT(hasMethodBarrier());
     JS_ASSERT(nativeContains(shape));
     JS_ASSERT(shape.isMethod());
     JS_ASSERT(shape.methodObject() == vp->toObject());
     JS_ASSERT(shape.writable());
     JS_ASSERT(shape.slot != SHAPE_INVALID_SLOT);
-    JS_ASSERT(shape.hasDefaultSetter() || shape.setterOp() == js_watch_set);
+    JS_ASSERT(shape.hasDefaultSetter());
     JS_ASSERT(!isGlobal());  /* i.e. we are not changing the global shape */
 
     JSObject *funobj = &vp->toObject();
     JSFunction *fun = funobj->getFunctionPrivate();
     JS_ASSERT(fun == funobj);
     JS_ASSERT(FUN_NULL_CLOSURE(fun));
 
     funobj = CloneFunctionObject(cx, fun, funobj->getParent());
--- a/js/src/jspropertycache.cpp
+++ b/js/src/jspropertycache.cpp
@@ -182,16 +182,18 @@ PropertyCache::fill(JSContext *cx, JSObj
 #endif
                         if (!pobj->brand(cx))
                             return JS_NO_PROP_CACHE_FILL;
                     }
                     vword.setFunObj(*funobj);
                     break;
                 }
             }
+        } else if ((cs->format & (JOF_SET | JOF_FOR | JOF_INCDEC)) && obj->watched()) {
+            return JS_NO_PROP_CACHE_FILL;
         }
 
         /*
          * If getting a value via a stub getter, or doing an INCDEC op
          * with stub getters and setters, we can cache the slot.
          */
         if (!(cs->format & (JOF_SET | JOF_FOR)) &&
             (!(cs->format & JOF_INCDEC) || (shape->hasDefaultSetter() && shape->writable())) &&
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -193,16 +193,18 @@ class Bindings;
 class MultiDeclRange;
 class ParseMapPool;
 class DefnOrHeader;
 typedef InlineMap<JSAtom *, JSDefinition *, 24> AtomDefnMap;
 typedef InlineMap<JSAtom *, jsatomid, 24> AtomIndexMap;
 typedef InlineMap<JSAtom *, DefnOrHeader, 24> AtomDOHMap;
 typedef Vector<UpvarCookie, 8> UpvarCookies;
 
+class WatchpointMap;
+
 } /* namespace js */
 
 } /* export "C++" */
 
 #else
 
 typedef struct JSAtom JSAtom;
 
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -57,17 +57,16 @@
 #include "jsdbgapi.h"
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsscope.h"
 #include "jsstr.h"
 #include "jstracer.h"
 
-#include "jsdbgapiinlines.h"
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 uint32
@@ -528,17 +527,17 @@ NormalizeGetterAndSetter(JSContext *cx, 
 {
     if (setter == StrictPropertyStub) {
         JS_ASSERT(!(attrs & JSPROP_SETTER));
         setter = NULL;
     }
     if (flags & Shape::METHOD) {
         /* Here, getter is the method, a function object reference. */
         JS_ASSERT(getter);
-        JS_ASSERT(!setter || setter == js_watch_set);
+        JS_ASSERT(!setter);
         JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
     } else {
         if (getter == PropertyStub) {
             JS_ASSERT(!(attrs & JSPROP_GETTER));
             getter = NULL;
         }
     }
 
@@ -637,23 +636,17 @@ JSObject::addProperty(JSContext *cx, jsi
         return NULL;
     }
 
     NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
 
     /* Search for id with adding = true in order to claim its entry. */
     Shape **spp = nativeSearch(id, true);
     JS_ASSERT(!SHAPE_FETCH(spp));
-    const Shape *shape = addPropertyInternal(cx, id, getter, setter, slot, attrs, 
-                                             flags, shortid, spp);
-    if (!shape)
-        return NULL;
-
-    /* Update any watchpoints referring to this property. */
-    return js_UpdateWatchpointsForShape(cx, this, shape);
+    return addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
 }
 
 const Shape *
 JSObject::addPropertyInternal(JSContext *cx, jsid id,
                               PropertyOp getter, StrictPropertyOp setter,
                               uint32 slot, uintN attrs,
                               uintN flags, intN shortid,
                               Shape **spp)
@@ -760,21 +753,17 @@ JSObject::putProperty(JSContext *cx, jsi
          * You can't add properties to a non-extensible object, but you can change
          * attributes of properties in such objects.
          */
         if (!isExtensible()) {
             reportNotExtensible(cx);
             return NULL;
         }
 
-        const Shape *newShape =
-            addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
-        if (!newShape)
-            return NULL;
-        return js_UpdateWatchpointsForShape(cx, this, newShape);
+        return addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
     }
 
     /* Property exists: search must have returned a valid *spp. */
     JS_ASSERT(!SHAPE_IS_REMOVED(*spp));
 
     if (!CheckCanChangeAttrs(cx, this, shape, &attrs))
         return NULL;
     
@@ -894,17 +883,17 @@ JSObject::putProperty(JSContext *cx, jsi
             freeSlot(cx, oldSlot);
         else
             setSlot(oldSlot, UndefinedValue());
         JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
     }
 
     CHECK_SHAPE_CONSISTENCY(this);
 
-    return js_UpdateWatchpointsForShape(cx, this, shape);
+    return shape;
 }
 
 const Shape *
 JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN mask,
                          PropertyOp getter, StrictPropertyOp setter)
 {
     JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen());
     JS_ASSERT(!JSID_IS_VOID(shape->propid));
@@ -960,20 +949,16 @@ JSObject::changeProperty(JSContext *cx, 
         mutableShape->attrs = uint8(attrs);
 
         updateFlags(shape);
 
         /* See the corresponding code in putProperty. */
         lastProp->shapeid = js_GenerateShape(cx);
         clearOwnShape();
 
-        shape = js_UpdateWatchpointsForShape(cx, this, shape);
-        if (!shape)
-            return NULL;
-        JS_ASSERT(shape == mutableShape);
         newShape = mutableShape;
     } else if (shape == lastProp) {
         Shape child(shape->propid, getter, setter, shape->slot, attrs, shape->flags,
                     shape->shortid);
 
         newShape = getChildProperty(cx, shape->parent, child);
 #ifdef DEBUG
         if (newShape) {
@@ -1209,17 +1194,17 @@ JSObject::methodShapeChange(JSContext *c
 
     JS_ASSERT(!JSID_IS_VOID(shape.propid));
     if (shape.isMethod()) {
 #ifdef DEBUG
         const Value &prev = nativeGetSlot(shape.slot);
         JS_ASSERT(shape.methodObject() == prev.toObject());
         JS_ASSERT(canHaveMethodBarrier());
         JS_ASSERT(hasMethodBarrier());
-        JS_ASSERT(!shape.rawSetter || shape.rawSetter == js_watch_set);
+        JS_ASSERT(!shape.rawSetter);
 #endif
 
         /*
          * Pass null to make a stub getter, but pass along shape.rawSetter to
          * preserve watchpoints. Clear Shape::METHOD from flags as we are
          * despecializing from a method memoized in the property tree to a
          * plain old function-valued property.
          */
--- a/js/src/jsscopeinlines.h
+++ b/js/src/jsscopeinlines.h
@@ -217,17 +217,17 @@ Shape::Shape(uint32 shapeid)
 }
 
 inline JSDHashNumber
 Shape::hash() const
 {
     JSDHashNumber hash = 0;
 
     /* Accumulate from least to most random so the low bits are most random. */
-    JS_ASSERT_IF(isMethod(), !rawSetter || rawSetter == js_watch_set);
+    JS_ASSERT_IF(isMethod(), !rawSetter);
     if (rawGetter)
         hash = JS_ROTATE_LEFT32(hash, 4) ^ jsuword(rawGetter);
     if (rawSetter)
         hash = JS_ROTATE_LEFT32(hash, 4) ^ jsuword(rawSetter);
     hash = JS_ROTATE_LEFT32(hash, 4) ^ (flags & PUBLIC_FLAGS);
     hash = JS_ROTATE_LEFT32(hash, 4) ^ attrs;
     hash = JS_ROTATE_LEFT32(hash, 4) ^ shortid;
     hash = JS_ROTATE_LEFT32(hash, 4) ^ slot;
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -12188,22 +12188,18 @@ TraceRecorder::record_AddProperty(JSObje
 {
     Value& objv = stackval(-2);
     JS_ASSERT(&objv.toObject() == obj);
     LIns* obj_ins = get(&objv);
     Value& v = stackval(-1);
     LIns* v_ins = get(&v);
     const Shape* shape = obj->lastProperty();
 
-    if (!shape->hasDefaultSetter()) {
-        JS_ASSERT(IsWatchedProperty(cx, shape));
-        RETURN_STOP_A("assignment adds property with watchpoint");
-    }
-
 #ifdef DEBUG
+    JS_ASSERT(shape->hasDefaultSetter());
     JS_ASSERT(addPropShapeBefore);
     if (obj->inDictionaryMode())
         JS_ASSERT(shape->previous()->matches(addPropShapeBefore));
     else
         JS_ASSERT(shape->previous() == addPropShapeBefore);
     JS_ASSERT(shape->isDataDescriptor());
     JS_ASSERT(shape->hasDefaultSetter());
     addPropShapeBefore = NULL;
@@ -12343,16 +12339,18 @@ TraceRecorder::setProperty(JSObject* obj
 {
     *deferredp = false;
 
     JSAtom *atom = atoms[GET_INDEX(cx->regs().pc)];
     jsid id = js_CheckForStringIndex(ATOM_TO_JSID(atom));
 
     if (obj->getOps()->setProperty)
         RETURN_STOP("non-native object");  // FIXME: bug 625900
+    if (obj->watched())
+        RETURN_STOP("watchpoint");
 
     bool safe;
     JSObject* pobj;
     const Shape* shape;
     CHECK_STATUS(lookupForSetPropertyOp(obj, obj_ins, id, &safe, &pobj, &shape));
     if (!safe)
         RETURN_STOP("setprop: lookup fail"); // FIXME: bug 625900
 
@@ -14040,18 +14038,18 @@ JS_DEFINE_CALLINFO_4(static, OBJECT_FAIL
  * or both slotp and v_insp. In the latter case, we require a plain old
  * property with a slot; if the property turns out to be anything else, abort
  * tracing (rather than emit a call to a native getter or GetAnyProperty).
  */
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, Value *outp)
 {
     /*
-     * Insist that obj have js_SetProperty as its set object-op. This suffices
-     * to prevent a rogue obj from being used on-trace (loaded via obj_ins),
+     * Insist that obj have NULL as its getProperty object-op. This suffices to
+     * prevent a rogue obj from being used on-trace (loaded via obj_ins),
      * because we will guard on shape (or else global object identity) and any
      * object not having the same op must have a different class, and therefore
      * must differ in its shape (or not be the global object).
      */
     if (!obj->isDenseArray() && obj->getOps()->getProperty)
         RETURN_STOP_A("non-dense-array, non-native js::ObjectOps::getProperty");
 
     JS_ASSERT((slotp && v_insp && !outp) || (!slotp && !v_insp && outp));
--- a/js/src/jstypedarray.cpp
+++ b/js/src/jstypedarray.cpp
@@ -321,17 +321,17 @@ ArrayBuffer::obj_setProperty(JSContext *
         }
         return true;
     }
 
     JSObject *delegate = DelegateObject(cx, obj);
     if (!delegate)
         return false;
 
-    return js_SetProperty(cx, delegate, id, vp, strict);
+    return js_SetPropertyHelper(cx, delegate, id, 0, vp, strict);
 }
 
 JSBool
 ArrayBuffer::obj_getAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
 {
     if (JSID_IS_ATOM(id, cx->runtime->atomState.byteLengthAtom)) {
         *attrsp = JSPROP_PERMANENT | JSPROP_READONLY;
         return true;
@@ -743,17 +743,17 @@ class TypedArrayTemplate
         JS_ASSERT(tarray);
 
         if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)) {
             vp->setNumber(tarray->length);
             return true;
         }
 
         jsuint index;
-        // We can't just chain to js_SetProperty, because we're not a normal object.
+        // We can't just chain to js_SetPropertyHelper, because we're not a normal object.
         if (!tarray->isArrayIndex(cx, id, &index)) {
 #if 0
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                  JSMSG_TYPED_ARRAY_BAD_INDEX);
             return false;
 #endif
             // Silent ignore is better than an exception here, because
             // at some point we may want to support other properties on
new file mode 100644
--- /dev/null
+++ b/js/src/jswatchpoint.cpp
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is an implementation of watchpoints for SpiderMonkey.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jason Orendorff <jorendorff@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "jswatchpoint.h"
+#include "jsatom.h"
+#include "jsgcmark.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+using namespace js::gc;
+
+inline HashNumber
+DefaultHasher<WatchKey>::hash(const Lookup &key)
+{
+    return DefaultHasher<JSObject *>::hash(key.object) ^ HashId(key.id);
+}
+
+class AutoEntryHolder {
+    typedef WatchpointMap::Map Map;
+    Map &map;
+    Map::Ptr p;
+    uint32 gen;
+    WatchKey key;
+
+  public:
+    AutoEntryHolder(Map &map, Map::Ptr p)
+        : map(map), p(p), gen(map.generation()), key(p->key) {
+        JS_ASSERT(!p->value.held);
+        p->value.held = true;
+    }
+
+    ~AutoEntryHolder() {
+        if (gen != map.generation())
+            p = map.lookup(key);
+        if (p)
+            p->value.held = false;
+    }
+};
+
+bool
+WatchpointMap::init()
+{
+    return map.init();
+}
+
+bool
+WatchpointMap::watch(JSContext *cx, JSObject *obj, jsid id,
+                     JSWatchPointHandler handler, JSObject *closure)
+{
+    JS_ASSERT(id == js_CheckForStringIndex(id));
+    obj->setWatched(cx);
+    Watchpoint w;
+    w.handler = handler;
+    w.closure = closure;
+    w.held = false;
+    if (!map.put(WatchKey(obj, id), w)) {
+        js_ReportOutOfMemory(cx);
+        return false;
+    }
+    return true;
+}
+
+void
+WatchpointMap::unwatch(JSObject *obj, jsid id,
+                       JSWatchPointHandler *handlerp, JSObject **closurep)
+{
+    JS_ASSERT(id == js_CheckForStringIndex(id));
+    if (Map::Ptr p = map.lookup(WatchKey(obj, id))) {
+        if (handlerp)
+            *handlerp = p->value.handler;
+        if (closurep)
+            *closurep = p->value.closure;
+        map.remove(p);
+    }
+}
+
+void
+WatchpointMap::unwatchObject(JSObject *obj)
+{
+    for (Map::Enum r(map); !r.empty(); r.popFront()) {
+        Map::Entry &e = r.front();
+        if (e.key.object == obj)
+            r.removeFront();
+    }
+}
+
+void
+WatchpointMap::clear()
+{
+    map.clear();
+}
+
+bool
+WatchpointMap::triggerWatchpoint(JSContext *cx, JSObject *obj, jsid id, Value *vp)
+{
+    JS_ASSERT(id == js_CheckForStringIndex(id));
+    Map::Ptr p = map.lookup(WatchKey(obj, id));
+    if (!p || p->value.held)
+        return true;
+
+    AutoEntryHolder holder(map, p);
+
+    /* Copy the entry, since GC would invalidate p. */
+    JSWatchPointHandler handler = p->value.handler;
+    JSObject *closure = p->value.closure;
+
+    /* Determine the property's old value. */
+    Value old;
+    old.setUndefined();
+    if (obj->isNative()) {
+        if (const Shape *shape = obj->nativeLookup(id)) {
+            uint32 slot = shape->slot;
+            if (obj->containsSlot(slot)) {
+                if (shape->isMethod()) {
+                    /*
+                     * The existing watched property is a method. Trip
+                     * the method read barrier in order to avoid
+                     * passing an uncloned function object to the
+                     * handler.
+                     */
+                    Value method = ObjectValue(shape->methodObject());
+                    if (!obj->methodReadBarrier(cx, *shape, &method))
+                        return false;
+                    shape = obj->nativeLookup(id);
+                    JS_ASSERT(shape->isDataDescriptor());
+                    JS_ASSERT(!shape->isMethod());
+                    old = method;
+                } else {
+                    old = obj->nativeGetSlot(slot);
+                }
+            }
+        }
+    }
+
+    /* Call the handler. */
+    return handler(cx, obj, id, Jsvalify(old), Jsvalify(vp), closure);
+}
+
+bool
+WatchpointMap::markAllIteratively(JSTracer *trc)
+{
+    JSRuntime *rt = trc->context->runtime;
+    if (rt->gcCurrentCompartment) {
+        WatchpointMap *wpmap = rt->gcCurrentCompartment->watchpointMap;
+        return wpmap && wpmap->markIteratively(trc);
+    }
+
+    bool mutated = false;
+    for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
+        if ((*c)->watchpointMap)
+            mutated |= (*c)->watchpointMap->markIteratively(trc);
+    }
+    return mutated;
+}
+
+bool
+WatchpointMap::markIteratively(JSTracer *trc)
+{
+    bool marked = false;
+    for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
+        Map::Entry &e = r.front();
+        bool objectIsLive = e.key.object->isMarked();
+        if (objectIsLive || e.value.held) {
+            if (!objectIsLive) {
+                MarkObject(trc, *e.key.object, "held Watchpoint object");
+                marked = true;
+            }
+
+            jsid id = e.key.id;
+            JS_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
+            MarkId(trc, id, "WatchKey::id");
+
+            if (e.value.closure && !e.value.closure->isMarked()) {
+                MarkObject(trc, *e.value.closure, "Watchpoint::closure");
+                marked = true;
+            }
+        }
+    }
+    return marked;
+}
+
+void
+WatchpointMap::sweepAll(JSRuntime *rt)
+{
+    if (rt->gcCurrentCompartment) {
+        if (WatchpointMap *wpmap = rt->gcCurrentCompartment->watchpointMap)
+            wpmap->sweep();
+    } else {
+        for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
+            if ((*c)->watchpointMap)
+                (*c)->watchpointMap->sweep();
+        }
+    }
+}
+
+void
+WatchpointMap::sweep()
+{
+    for (Map::Enum r(map); !r.empty(); r.popFront()) {
+        Map::Entry &e = r.front();
+        if (!e.key.object->isMarked()) {
+            JS_ASSERT(!e.value.held);
+            r.removeFront();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jswatchpoint.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is an implementation of watchpoints for SpiderMonkey.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jason Orendorff <jorendorff@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef jswatchpoint_h___
+#define jswatchpoint_h___
+
+#include "jshashtable.h"
+#include "jsprvtd.h"
+#include "jsapi.h"
+#include "jsvalue.h"
+
+namespace js {
+
+struct WatchKey {
+    WatchKey() {}
+    WatchKey(JSObject *obj, jsid id) : object(obj), id(id) {}
+    JSObject *object;
+    jsid id;
+};
+
+struct Watchpoint {
+    JSWatchPointHandler handler;
+    JSObject *closure;
+    bool held;  /* true if currently running handler */
+};
+
+template <>
+struct DefaultHasher<WatchKey> {
+    typedef WatchKey Lookup;
+    static inline js::HashNumber hash(const Lookup &key);
+
+    static bool match(const WatchKey &k, const Lookup &l) {
+        return k.object == l.object && k.id == l.id;
+    }
+};
+
+class WatchpointMap {
+  public:
+    typedef HashMap<WatchKey, Watchpoint, DefaultHasher<WatchKey>, SystemAllocPolicy> Map;
+
+    bool init();
+    bool watch(JSContext *cx, JSObject *obj, jsid id,
+               JSWatchPointHandler handler, JSObject *closure);
+    void unwatch(JSObject *obj, jsid id,
+                 JSWatchPointHandler *handlerp, JSObject **closurep);
+    void unwatchObject(JSObject *obj);
+    void clear();
+
+    bool triggerWatchpoint(JSContext *cx, JSObject *obj, jsid id, Value *vp);
+
+    static bool markAllIteratively(JSTracer *trc);
+    bool markIteratively(JSTracer *trc);
+    static void sweepAll(JSRuntime *rt);
+    void sweep();
+
+  private:
+    Map map;
+};
+
+}
+
+#endif /* jswatchpoint_h___ */
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -4153,17 +4153,17 @@ PutProperty(JSContext *cx, JSObject *obj
     } else {
         /*
          * ECMA-357 9.2.1.2/9.1.1.2 qname case.
          */
         nameqn = ToXMLName(cx, IdToJsval(id), &funid);
         if (!nameqn)
             goto bad;
         if (!JSID_IS_VOID(funid)) {
-            ok = js_SetProperty(cx, obj, funid, Valueify(vp), false);
+            ok = js_SetPropertyHelper(cx, obj, funid, 0, Valueify(vp), false);
             goto out;
         }
         nameobj = nameqn;
         roots[ID_ROOT] = OBJECT_TO_JSVAL(nameobj);
 
         if (xml->xml_class == JSXML_CLASS_LIST) {
             /*
              * Step 3 of 9.2.1.2.
--- a/js/src/methodjit/MonoIC.cpp
+++ b/js/src/methodjit/MonoIC.cpp
@@ -264,19 +264,20 @@ UpdateSetGlobalName(VMFrame &f, ic::SetG
 {
     /* Give globals a chance to appear. */
     if (!shape)
         return Lookup_Uncacheable;
 
     if (shape->isMethod() ||
         !shape->hasDefaultSetter() ||
         !shape->writable() ||
-        !shape->hasSlot())
+        !shape->hasSlot() ||
+        obj->watched())
     {
-        /* Disable the IC for weird shape attributes. */
+        /* Disable the IC for weird shape attributes and watchpoints. */
         PatchSetFallback(f, ic);
         return Lookup_Uncacheable;
     }
 
     /* Branded sets must guard that they don't overwrite method-valued properties. */
     if (obj->branded()) {
         /*
          * If this slot has a function valued property, the tail of this opcode
--- a/js/src/methodjit/PolyIC.cpp
+++ b/js/src/methodjit/PolyIC.cpp
@@ -485,16 +485,18 @@ class SetPropCompiler : public PICStubCo
     LookupStatus update()
     {
         JS_ASSERT(pic.hit);
 
         if (obj->isDenseArray())
             return disable("dense array");
         if (!obj->isNative())
             return disable("non-native");
+        if (obj->watched())
+            return disable("watchpoint");
 
         Class *clasp = obj->getClass();
 
         if (clasp->setProperty != StrictPropertyStub)
             return disable("set property hook");
         if (clasp->ops.lookupProperty)
             return disable("ops lookup property hook");
         if (clasp->ops.setProperty)
--- a/js/src/tests/js1_8_1/extensions/regress-452498-193.js
+++ b/js/src/tests/js1_8_1/extensions/regress-452498-193.js
@@ -50,16 +50,17 @@ test();
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 // Assertion failure: afunbox->parent, at ../jsparse.cpp:1912
 
+  this.x = undefined;
   this.watch("x", Function);
   NaN = uneval({ get \u3056 (){ return undefined } });
   x+=NaN;
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_1/extensions/regress-452498-196.js
+++ b/js/src/tests/js1_8_1/extensions/regress-452498-196.js
@@ -50,16 +50,17 @@ test();
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 // Assertion failure: localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST, at ../jsfun.cpp:916
 
+  this.x = undefined;
   this.watch("x", Function);
   NaN = uneval({ get \u3056 (){ return undefined } });
   x+=NaN;
 
   reportCompare(expect, actual, summary + ': 1');
 
 // Assertion failure: lexdep->isLet(), at ../jsparse.cpp:1900
 
--- a/js/src/tests/js1_8_5/extensions/jstests.list
+++ b/js/src/tests/js1_8_5/extensions/jstests.list
@@ -37,13 +37,14 @@ script regress-627984-3.js
 script regress-627984-4.js
 script regress-627984-5.js
 script regress-627984-6.js
 script regress-627984-7.js
 script regress-630377.js
 script regress-631723.js
 skip-if(!xulRuntime.shell) script regress-636818.js
 script regress-636697.js
+script regress-637985.js
 script is-generator.js
 script weakmap.js
 script regress-645160.js
 script regress-650753.js
 script regress-668438.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/regress-637985.js
@@ -0,0 +1,8 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+var obj = {};
+obj.watch(-1, function(){});
+obj.unwatch("-1");  // don't assert
+
+reportCompare(0, 0, 'ok');
\ No newline at end of file