Before I realized that PersistentRooted was fine for this, I went
through these other options. Posting here for posterity. I should
probably add them to the embedding examples.
----
There are a couple of options.
One would be to specialize templates appropriately to understand how to
trace and sweep std::map. Then you could use
PersistentRooted<std::map<std::string, JS::Heap<JSObject*>>>:
namespace JS {
template <typename NonGCKeyT, typename GCValueT>
struct GCPolicy<std::map<NonGCKeyT, GCValueT>> {
using Map = std::map<NonGCKeyT, GCValueT>;
static void trace(JSTracer* trc, Map* map, const char* name) {
for (auto& ent : *map) {
GCPolicy<GCValueT>::trace(trc, &ent.second, "map value");
}
}
static void sweep(Map* map) {
auto iter = map->begin();
while (iter != map->end()) {
if (GCPolicy<GCValueT>::needsSweep(&iter->second))
iter = map->erase(iter);
else
++iter;
}
}
static bool needsSweep(Map* map) {
return !map->empty();
}
static bool isValid(Map* map) { return true; }
};
} /* namespace JS */
using ObjMap = std::map<std::string, JS::Heap<JSObject*>>;
static JS::PersistentRooted<ObjMap> rootedObjectsMap;
Note that this only suffices for maps with non-GC types as keys. If you
were mapping from a JSObject*, for example, then our moving GC could
invalidate the pointer and corrupt your std::map (and you can't just
update the pointer there, because it'll hash differently, and anyway STL
makes the key const so you don't do that.) The Heap<T> is on the key for
the post-write barrier: if you put a nursery pointer into this map, we
need to remember where it is so we can update it when the object moves.
And if you're using incremental GC, it'll also give a pre-write barrier
to prevent any pointers from hiding in the basement while the marking
storm passes by overhead.
The sweep() implementation up there is not used in your case, since the
only instance is rooted and thus everything will always be alive. But it
seemed like a footgun to leave it out, and having it will allow
JS::Heap<std::map<...>> (still for non-GC keys only, though.)
PersistentRooted is a tricky beast. You'll need to initialize it in some
startup code, after you've constructed the runtime and everything, and
you need to ensure that you reset() it before shutting down or we'll
assert in a debug build (you'd be keeping objects live at shutdown,
leaking random stuff and possibly preventing finalizers from running.)
During initialziation:
rootedObjectsMap.init(cx);
and before everything shuts down:
rootedObjectsMap.reset();
Using it would just be what you're used to:
rooted.get().insert(ObjMap::value_type(std::string("foo"), obj));
fprintf(stderr, "foo -> %p\n", (void*) rooted.get()[std::string("foo")]);
Another option would be to use our GC-aware map API, GCHashMap. But
again, you'd have to do template stuff because we don't natively know
how to hash std::string, nor do we know that it's not a GC pointer type
so it doesn't need to be traced or die during GC:
namespace mozilla {
template <>
struct DefaultHasher<std::string> {
using Key = std::string;
using Lookup = Key;
static HashNumber hash(const Lookup& aLookup) {
return HashString(aLookup.c_str());
}
static bool match(const Key& aKey, const Lookup& aLookup) {
return aKey == aLookup;
}
};
} /* namespace mozilla */
namespace JS {
template <>
struct GCPolicy<std::string> : public IgnoreGCPolicy<std::string> {};
} /* namespace JS */
using ObjMap = JS::GCHashMap<std::string, JS::Heap<JSObject*>,
js::DefaultHasher<std::string>, js::SystemAllocPolicy>;
The API is very different from std::map, though:
rooted.put(std::string("foo"), obj);
fprintf(stderr, "foo -> %p\n", (void*)
rooted.lookup(std::string("foo"))->value());