Bug 982561 - Add tests for weak maps with key delegates r=terrence
authorJon Coppeard <jcoppeard@mozilla.com>
Fri, 16 May 2014 09:56:50 +0100
changeset 183497 67f5286dda31cd67d55396c5973c46d301049910
parent 183496 36eba9f589835a35fc5eaac1a373be47ae032003
child 183498 8475dbade7b3cb2f748e4deada61710325c28001
push id26793
push userryanvm@gmail.com
push dateFri, 16 May 2014 18:53:54 +0000
treeherdermozilla-central@eb2a6f7785a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs982561
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 982561 - Add tests for weak maps with key delegates r=terrence
js/src/gc/Zone.h
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testWeakMap.cpp
js/src/jsgc.cpp
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef gc_Zone_h
 #define gc_Zone_h
 
 #include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "jscntxt.h"
 #include "jsgc.h"
 #include "jsinfer.h"
 
 #include "gc/FindSCCs.h"
 
@@ -142,16 +143,17 @@ struct Zone : public JS::shadow::Zone,
         Sweep,
         Finished
     };
 
   private:
     bool                         gcScheduled;
     GCState                      gcState;
     bool                         gcPreserveCode;
+    mozilla::DebugOnly<unsigned> gcLastZoneGroupIndex;
 
   public:
     bool isCollecting() const {
         if (runtimeFromMainThread()->isHeapCollecting())
             return gcState != NoGC;
         else
             return needsBarrier();
     }
@@ -223,16 +225,26 @@ struct Zone : public JS::shadow::Zone,
     bool isGCSweeping() {
         return gcState == Sweep;
     }
 
     bool isGCFinished() {
         return gcState == Finished;
     }
 
+#ifdef DEBUG
+    /*
+     * For testing purposes, return the index of the zone group which this zone
+     * was swept in in the last GC.
+     */
+    unsigned lastZoneGroupIndex() {
+        return gcLastZoneGroupIndex;
+    }
+#endif
+
     /* This is updated by both the main and GC helper threads. */
     mozilla::Atomic<size_t, mozilla::ReleaseAcquire> gcBytes;
 
     size_t                       gcTriggerBytes;
     size_t                       gcMaxMallocBytes;
     double                       gcHeapGrowthFactor;
 
     bool                         isSystem;
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -67,16 +67,17 @@ UNIFIED_SOURCES += [
     'testSourcePolicy.cpp',
     'testStringBuffer.cpp',
     'testStructuredClone.cpp',
     'testToIntWidth.cpp',
     'testTrap.cpp',
     'testTypedArrays.cpp',
     'testUncaughtError.cpp',
     'testUTF8.cpp',
+    'testWeakMap.cpp',
     'testXDR.cpp',
 ]
 
 if CONFIG['ENABLE_ION']:
     UNIFIED_SOURCES += [
         'testJitRValueAlloc.cpp',
     ]
 
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+* vim: set ts=8 sts=4 et sw=4 tw=99:
+*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gc/Zone.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+#ifdef JSGC_USE_EXACT_ROOTING
+
+BEGIN_TEST(testWeakMap_basicOperations)
+{
+    RootedObject map(cx, NewWeakMapObject(cx));
+    CHECK(IsWeakMapObject(map));
+
+    RootedObject key(cx, newKey());
+    CHECK(key);
+    CHECK(!IsWeakMapObject(key));
+
+    RootedValue r(cx);
+    CHECK(GetWeakMapEntry(cx, map, key, &r));
+    CHECK(r.isUndefined());
+
+    CHECK(checkSize(map, 0));
+
+    RootedValue val(cx, Int32Value(1));
+    CHECK(SetWeakMapEntry(cx, map, key, val));
+
+    CHECK(GetWeakMapEntry(cx, map, key, &r));
+    CHECK(r == val);
+    CHECK(checkSize(map, 1));
+
+    JS_GC(rt);
+
+    CHECK(GetWeakMapEntry(cx, map, key, &r));
+    CHECK(r == val);
+    CHECK(checkSize(map, 1));
+
+    key = nullptr;
+    JS_GC(rt);
+
+    CHECK(checkSize(map, 0));
+
+    return true;
+}
+
+JSObject *newKey()
+{
+    return JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+}
+
+bool
+checkSize(HandleObject map, uint32_t expected)
+{
+    RootedObject keys(cx);
+    CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+    uint32_t length;
+    CHECK(JS_GetArrayLength(cx, keys, &length));
+    CHECK(length == expected);
+
+    return true;
+}
+END_TEST(testWeakMap_basicOperations)
+
+BEGIN_TEST(testWeakMap_keyDelegates)
+{
+    JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+    JS_GC(rt);
+
+    RootedObject map(cx, NewWeakMapObject(cx));
+    CHECK(map);
+
+    RootedObject key(cx, newKey());
+    CHECK(key);
+
+    RootedObject delegate(cx, newDelegate());
+    CHECK(delegate);
+
+    SetKeyDelegate(key, delegate);
+
+    /*
+     * Perform an incremental GC, introducing an unmarked CCW to force the map
+     * zone to finish marking before the delegate zone.
+     */
+    CHECK(newCCW(map, delegate));
+    GCDebugSlice(rt, true, 1000000);
+#ifdef DEBUG
+    CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex());
+#endif
+
+    /* Add our entry to the weakmap. */
+    RootedValue val(cx, Int32Value(1));
+    CHECK(SetWeakMapEntry(cx, map, key, val));
+    CHECK(checkSize(map, 1));
+
+    /* Check the delegate keeps the entry alive even if the key is not reachable. */
+    key = nullptr;
+    CHECK(newCCW(map, delegate));
+    GCDebugSlice(rt, true, 100000);
+    CHECK(checkSize(map, 1));
+
+    /*
+     * Check that the zones finished marking at the same time, which is
+     * neccessary because of the presence of the delegate and the CCW.
+     */
+#ifdef DEBUG
+    CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex());
+#endif
+
+    /* Check that when the delegate becomes unreacable the entry is removed. */
+    delegate = nullptr;
+    JS_GC(rt);
+    CHECK(checkSize(map, 0));
+
+    return true;
+}
+
+static void SetKeyDelegate(JSObject *key, JSObject *delegate)
+{
+    JS_SetPrivate(key, delegate);
+}
+
+static JSObject *GetKeyDelegate(JSObject *obj)
+{
+    return static_cast<JSObject*>(JS_GetPrivate(obj));
+}
+
+JSObject *newKey()
+{
+    static const js::Class keyClass = {
+        "keyWithDelgate",
+        JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
+        JS_PropertyStub,         /* addProperty */
+        JS_DeletePropertyStub,   /* delProperty */
+        JS_PropertyStub,         /* getProperty */
+        JS_StrictPropertyStub,   /* setProperty */
+        JS_EnumerateStub,
+        JS_ResolveStub,
+        JS_ConvertStub,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        JS_NULL_CLASS_SPEC,
+        {
+            nullptr,
+            nullptr,
+            nullptr,
+            false,
+            GetKeyDelegate
+        },
+        JS_NULL_OBJECT_OPS
+    };
+
+    RootedObject key(cx);
+    key = JS_NewObject(cx,
+                       reinterpret_cast<const JSClass *>(&keyClass),
+                       JS::NullPtr(),
+                       JS::NullPtr());
+    if (!key)
+        return nullptr;
+
+    SetKeyDelegate(key, nullptr);
+
+    return key;
+}
+
+JSObject *newCCW(HandleObject sourceZone, HandleObject destZone)
+{
+    /*
+     * Now ensure that this zone will be swept first by adding a cross
+     * compartment wrapper to a new objct in the same zone as the
+     * delegate obejct.
+     */
+    RootedObject object(cx);
+    {
+        JSAutoCompartment ac(cx, destZone);
+        object = JS_NewObject(cx, nullptr, NullPtr(), NullPtr());
+        if (!object)
+            return nullptr;
+    }
+    {
+        JSAutoCompartment ac(cx, sourceZone);
+        if (!JS_WrapObject(cx, &object))
+            return nullptr;
+    }
+    return object;
+}
+
+JSObject *newDelegate()
+{
+    static const JSClass delegateClass = {
+        "delegate",
+        JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
+        JS_PropertyStub,
+        JS_DeletePropertyStub,
+        JS_PropertyStub,
+        JS_StrictPropertyStub,
+        JS_EnumerateStub,
+        JS_ResolveStub,
+        JS_ConvertStub,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        JS_GlobalObjectTraceHook
+    };
+
+    /* Create the global object. */
+    JS::CompartmentOptions options;
+    options.setVersion(JSVERSION_LATEST);
+    JS::RootedObject global(cx);
+    global = JS_NewGlobalObject(cx, &delegateClass, nullptr, JS::FireOnNewGlobalHook, options);
+    JS_SetReservedSlot(global, 0, Int32Value(42));
+
+    /*
+     * Ensure the delegate is not in the nursery because for the purpose of this
+     * test we're going to put it in a private slot where it won't get updated.
+     */
+    JS_GC(rt);
+
+    return global;
+}
+
+bool
+checkSize(HandleObject map, uint32_t expected)
+{
+    RootedObject keys(cx);
+    CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+    uint32_t length;
+    CHECK(JS_GetArrayLength(cx, keys, &length));
+    CHECK(length == expected);
+
+    return true;
+}
+END_TEST(testWeakMap_keyDelegates)
+
+#endif // JSGC_USE_EXACT_ROOTING
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3810,16 +3810,18 @@ GCRuntime::beginSweepingZoneGroup()
         /* Purge the ArenaLists before sweeping. */
         zone->allocator.arenas.purge();
 
         if (rt->isAtomsZone(zone))
             sweepingAtoms = true;
 
         if (rt->sweepZoneCallback)
             rt->sweepZoneCallback(zone);
+
+        zone->gcLastZoneGroupIndex = zoneGroupIndex;
     }
 
     validateIncrementalMarking();
 
     FreeOp fop(rt, sweepOnBackgroundThread);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);