949 lines
33 KiB
C++
949 lines
33 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* 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_Barrier_h
|
|
#define gc_Barrier_h
|
|
|
|
#include "NamespaceImports.h"
|
|
|
|
#include "gc/Heap.h"
|
|
#include "gc/StoreBuffer.h"
|
|
#include "js/HeapAPI.h"
|
|
#include "js/Id.h"
|
|
#include "js/RootingAPI.h"
|
|
#include "js/Value.h"
|
|
|
|
/*
|
|
* A write barrier is a mechanism used by incremental or generation GCs to
|
|
* ensure that every value that needs to be marked is marked. In general, the
|
|
* write barrier should be invoked whenever a write can cause the set of things
|
|
* traced through by the GC to change. This includes:
|
|
* - writes to object properties
|
|
* - writes to array slots
|
|
* - writes to fields like JSObject::shape_ that we trace through
|
|
* - writes to fields in private data
|
|
* - writes to non-markable fields like JSObject::private that point to
|
|
* markable data
|
|
* The last category is the trickiest. Even though the private pointers does not
|
|
* point to a GC thing, changing the private pointer may change the set of
|
|
* objects that are traced by the GC. Therefore it needs a write barrier.
|
|
*
|
|
* Every barriered write should have the following form:
|
|
* <pre-barrier>
|
|
* obj->field = value; // do the actual write
|
|
* <post-barrier>
|
|
* The pre-barrier is used for incremental GC and the post-barrier is for
|
|
* generational GC.
|
|
*
|
|
* PRE-BARRIER
|
|
*
|
|
* To understand the pre-barrier, let's consider how incremental GC works. The
|
|
* GC itself is divided into "slices". Between each slice, JS code is allowed to
|
|
* run. Each slice should be short so that the user doesn't notice the
|
|
* interruptions. In our GC, the structure of the slices is as follows:
|
|
*
|
|
* 1. ... JS work, which leads to a request to do GC ...
|
|
* 2. [first GC slice, which performs all root marking and possibly more marking]
|
|
* 3. ... more JS work is allowed to run ...
|
|
* 4. [GC mark slice, which runs entirely in drainMarkStack]
|
|
* 5. ... more JS work ...
|
|
* 6. [GC mark slice, which runs entirely in drainMarkStack]
|
|
* 7. ... more JS work ...
|
|
* 8. [GC marking finishes; sweeping done non-incrementally; GC is done]
|
|
* 9. ... JS continues uninterrupted now that GC is finishes ...
|
|
*
|
|
* Of course, there may be a different number of slices depending on how much
|
|
* marking is to be done.
|
|
*
|
|
* The danger inherent in this scheme is that the JS code in steps 3, 5, and 7
|
|
* might change the heap in a way that causes the GC to collect an object that
|
|
* is actually reachable. The write barrier prevents this from happening. We use
|
|
* a variant of incremental GC called "snapshot at the beginning." This approach
|
|
* guarantees the invariant that if an object is reachable in step 2, then we
|
|
* will mark it eventually. The name comes from the idea that we take a
|
|
* theoretical "snapshot" of all reachable objects in step 2; all objects in
|
|
* that snapshot should eventually be marked. (Note that the write barrier
|
|
* verifier code takes an actual snapshot.)
|
|
*
|
|
* The basic correctness invariant of a snapshot-at-the-beginning collector is
|
|
* that any object reachable at the end of the GC (step 9) must either:
|
|
* (1) have been reachable at the beginning (step 2) and thus in the snapshot
|
|
* (2) or must have been newly allocated, in steps 3, 5, or 7.
|
|
* To deal with case (2), any objects allocated during an incremental GC are
|
|
* automatically marked black.
|
|
*
|
|
* This strategy is actually somewhat conservative: if an object becomes
|
|
* unreachable between steps 2 and 8, it would be safe to collect it. We won't,
|
|
* mainly for simplicity. (Also, note that the snapshot is entirely
|
|
* theoretical. We don't actually do anything special in step 2 that we wouldn't
|
|
* do in a non-incremental GC.
|
|
*
|
|
* It's the pre-barrier's job to maintain the snapshot invariant. Consider the
|
|
* write "obj->field = value". Let the prior value of obj->field be
|
|
* value0. Since it's possible that value0 may have been what obj->field
|
|
* contained in step 2, when the snapshot was taken, the barrier marks
|
|
* value0. Note that it only does this if we're in the middle of an incremental
|
|
* GC. Since this is rare, the cost of the write barrier is usually just an
|
|
* extra branch.
|
|
*
|
|
* In practice, we implement the pre-barrier differently based on the type of
|
|
* value0. E.g., see JSObject::writeBarrierPre, which is used if obj->field is
|
|
* a JSObject*. It takes value0 as a parameter.
|
|
*
|
|
* READ-BARRIER
|
|
*
|
|
* Incremental GC requires that weak pointers have read barriers. The problem
|
|
* happens when, during an incremental GC, some code reads a weak pointer and
|
|
* writes it somewhere on the heap that has been marked black in a previous
|
|
* slice. Since the weak pointer will not otherwise be marked and will be swept
|
|
* and finalized in the last slice, this will leave the pointer just written
|
|
* dangling after the GC. To solve this, we immediately mark black all weak
|
|
* pointers that get read between slices so that it is safe to store them in an
|
|
* already marked part of the heap, e.g. in Rooted.
|
|
*
|
|
* POST-BARRIER
|
|
*
|
|
* For generational GC, we want to be able to quickly collect the nursery in a
|
|
* minor collection. Part of the way this is achieved is to only mark the
|
|
* nursery itself; tenured things, which may form the majority of the heap, are
|
|
* not traced through or marked. This leads to the problem of what to do about
|
|
* tenured objects that have pointers into the nursery: if such things are not
|
|
* marked, they may be discarded while there are still live objects which
|
|
* reference them. The solution is to maintain information about these pointers,
|
|
* and mark their targets when we start a minor collection.
|
|
*
|
|
* The pointers can be thought of as edges in object graph, and the set of edges
|
|
* from the tenured generation into the nursery is know as the remembered set.
|
|
* Post barriers are used to track this remembered set.
|
|
*
|
|
* Whenever a slot which could contain such a pointer is written, we use a write
|
|
* barrier to check if the edge created is in the remembered set, and if so we
|
|
* insert it into the store buffer, which is the collector's representation of
|
|
* the remembered set. This means than when we come to do a minor collection we
|
|
* can examine the contents of the store buffer and mark any edge targets that
|
|
* are in the nursery.
|
|
*
|
|
* IMPLEMENTATION DETAILS
|
|
*
|
|
* Since it would be awkward to change every write to memory into a function
|
|
* call, this file contains a bunch of C++ classes and templates that use
|
|
* operator overloading to take care of barriers automatically. In many cases,
|
|
* all that's necessary to make some field be barriered is to replace
|
|
* Type* field;
|
|
* with
|
|
* GCPtr<Type> field;
|
|
*
|
|
* One additional note: not all object writes need to be pre-barriered. Writes
|
|
* to newly allocated objects do not need a pre-barrier. In these cases, we use
|
|
* the "obj->field.init(value)" method instead of "obj->field = value". We use
|
|
* the init naming idiom in many places to signify that a field is being
|
|
* assigned for the first time.
|
|
*
|
|
* This file implements four classes, illustrated here:
|
|
*
|
|
* BarrieredBase base class of all barriers
|
|
* | |
|
|
* | WriteBarrieredBase base class which provides common write operations
|
|
* | | | | |
|
|
* | | | | PreBarriered provides pre-barriers only
|
|
* | | | |
|
|
* | | | GCPtr provides pre- and post-barriers
|
|
* | | |
|
|
* | | HeapPtr provides pre- and post-barriers; is relocatable
|
|
* | | and deletable for use inside C++ managed memory
|
|
* | |
|
|
* | HeapSlot similar to GCPtr, but tailored to slots storage
|
|
* |
|
|
* ReadBarrieredBase base class which provides common read operations
|
|
* |
|
|
* ReadBarriered provides read barriers only
|
|
*
|
|
*
|
|
* The implementation of the barrier logic is implemented on T::writeBarrier.*,
|
|
* via:
|
|
*
|
|
* WriteBarrieredBase<T>::pre
|
|
* -> InternalBarrierMethods<T*>::preBarrier
|
|
* -> T::writeBarrierPre
|
|
* -> InternalBarrierMethods<Value>::preBarrier
|
|
* -> InternalBarrierMethods<jsid>::preBarrier
|
|
* -> InternalBarrierMethods<T*>::preBarrier
|
|
* -> T::writeBarrierPre
|
|
*
|
|
* GCPtr<T>::post and HeapPtr<T>::post
|
|
* -> InternalBarrierMethods<T*>::postBarrier
|
|
* -> T::writeBarrierPost
|
|
* -> InternalBarrierMethods<Value>::postBarrier
|
|
* -> StoreBuffer::put
|
|
*
|
|
* These classes are designed to be used by the internals of the JS engine.
|
|
* Barriers designed to be used externally are provided in js/RootingAPI.h.
|
|
* These external barriers call into the same post-barrier implementations at
|
|
* InternalBarrierMethods<T>::post via an indirect call to Heap(.+)Barrier.
|
|
*
|
|
* These clases are designed to be used to wrap GC thing pointers or values that
|
|
* act like them (i.e. JS::Value and jsid). It is possible to use them for
|
|
* other types by supplying the necessary barrier implementations but this
|
|
* is not usually necessary and should be done with caution.
|
|
*/
|
|
|
|
class JSAtom;
|
|
struct JSCompartment;
|
|
class JSFlatString;
|
|
class JSLinearString;
|
|
|
|
namespace JS {
|
|
class Symbol;
|
|
} // namespace JS
|
|
|
|
namespace js {
|
|
|
|
class AccessorShape;
|
|
class ArrayObject;
|
|
class ArgumentsObject;
|
|
class ArrayBufferObjectMaybeShared;
|
|
class ArrayBufferObject;
|
|
class ArrayBufferViewObject;
|
|
class SharedArrayBufferObject;
|
|
class BaseShape;
|
|
class DebugEnvironmentProxy;
|
|
class GlobalObject;
|
|
class LazyScript;
|
|
class ModuleObject;
|
|
class ModuleEnvironmentObject;
|
|
class ModuleNamespaceObject;
|
|
class NativeObject;
|
|
class PlainObject;
|
|
class PropertyName;
|
|
class SavedFrame;
|
|
class EnvironmentObject;
|
|
class ScriptSourceObject;
|
|
class Shape;
|
|
class UnownedBaseShape;
|
|
class ObjectGroup;
|
|
|
|
namespace jit {
|
|
class JitCode;
|
|
} // namespace jit
|
|
|
|
#ifdef DEBUG
|
|
// Barriers can't be triggered during backend Ion compilation, which may run on
|
|
// a helper thread.
|
|
bool
|
|
CurrentThreadIsIonCompiling();
|
|
|
|
bool
|
|
CurrentThreadIsIonCompilingSafeForMinorGC();
|
|
|
|
bool
|
|
CurrentThreadIsGCSweeping();
|
|
|
|
bool
|
|
IsMarkedBlack(NativeObject* obj);
|
|
#endif
|
|
|
|
namespace gc {
|
|
|
|
// Marking.h depends on these barrier definitions, so we need a separate
|
|
// entry point for marking to implement the pre-barrier.
|
|
void MarkValueForBarrier(JSTracer* trc, Value* v, const char* name);
|
|
void MarkIdForBarrier(JSTracer* trc, jsid* idp, const char* name);
|
|
|
|
} // namespace gc
|
|
|
|
template <typename T>
|
|
struct InternalBarrierMethods {};
|
|
|
|
template <typename T>
|
|
struct InternalBarrierMethods<T*>
|
|
{
|
|
static bool isMarkable(T* v) { return v != nullptr; }
|
|
|
|
static bool isMarkableTaggedPointer(T* v) { return !IsNullTaggedPointer(v); }
|
|
|
|
static void preBarrier(T* v) { T::writeBarrierPre(v); }
|
|
|
|
static void postBarrier(T** vp, T* prev, T* next) { T::writeBarrierPost(vp, prev, next); }
|
|
|
|
static void readBarrier(T* v) { T::readBarrier(v); }
|
|
};
|
|
|
|
template <typename S> struct PreBarrierFunctor : public VoidDefaultAdaptor<S> {
|
|
template <typename T> void operator()(T* t);
|
|
};
|
|
|
|
template <typename S> struct ReadBarrierFunctor : public VoidDefaultAdaptor<S> {
|
|
template <typename T> void operator()(T* t);
|
|
};
|
|
|
|
template <>
|
|
struct InternalBarrierMethods<Value>
|
|
{
|
|
static bool isMarkable(const Value& v) { return v.isGCThing(); }
|
|
static bool isMarkableTaggedPointer(const Value& v) { return isMarkable(v); }
|
|
|
|
static void preBarrier(const Value& v) {
|
|
DispatchTyped(PreBarrierFunctor<Value>(), v);
|
|
}
|
|
|
|
static void postBarrier(Value* vp, const Value& prev, const Value& next) {
|
|
MOZ_ASSERT(!CurrentThreadIsIonCompiling());
|
|
MOZ_ASSERT(vp);
|
|
|
|
// If the target needs an entry, add it.
|
|
js::gc::StoreBuffer* sb;
|
|
if (next.isObject() && (sb = reinterpret_cast<gc::Cell*>(&next.toObject())->storeBuffer())) {
|
|
// If we know that the prev has already inserted an entry, we can
|
|
// skip doing the lookup to add the new entry. Note that we cannot
|
|
// safely assert the presence of the entry because it may have been
|
|
// added via a different store buffer.
|
|
if (prev.isObject() && reinterpret_cast<gc::Cell*>(&prev.toObject())->storeBuffer())
|
|
return;
|
|
sb->putValue(vp);
|
|
return;
|
|
}
|
|
// Remove the prev entry if the new value does not need it.
|
|
if (prev.isObject() && (sb = reinterpret_cast<gc::Cell*>(&prev.toObject())->storeBuffer()))
|
|
sb->unputValue(vp);
|
|
}
|
|
|
|
static void readBarrier(const Value& v) {
|
|
DispatchTyped(ReadBarrierFunctor<Value>(), v);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct InternalBarrierMethods<jsid>
|
|
{
|
|
static bool isMarkable(jsid id) { return JSID_IS_GCTHING(id); }
|
|
static bool isMarkableTaggedPointer(jsid id) { return isMarkable(id); }
|
|
|
|
static void preBarrier(jsid id) { DispatchTyped(PreBarrierFunctor<jsid>(), id); }
|
|
static void postBarrier(jsid* idp, jsid prev, jsid next) {}
|
|
};
|
|
|
|
// Barrier classes can use Mixins to add methods to a set of barrier
|
|
// instantiations, to make the barriered thing look and feel more like the
|
|
// thing itself.
|
|
template <typename T>
|
|
class BarrieredBaseMixins {};
|
|
|
|
// Base class of all barrier types.
|
|
//
|
|
// This is marked non-memmovable since post barriers added by derived classes
|
|
// can add pointers to class instances to the store buffer.
|
|
template <typename T>
|
|
class MOZ_NON_MEMMOVABLE BarrieredBase : public BarrieredBaseMixins<T>
|
|
{
|
|
protected:
|
|
// BarrieredBase is not directly instantiable.
|
|
explicit BarrieredBase(const T& v) : value(v) {}
|
|
|
|
// Storage for all barrier classes. |value| must be a GC thing reference
|
|
// type: either a direct pointer to a GC thing or a supported tagged
|
|
// pointer that can reference GC things, such as JS::Value or jsid. Nested
|
|
// barrier types are NOT supported. See assertTypeConstraints.
|
|
T value;
|
|
|
|
public:
|
|
// Note: this is public because C++ cannot friend to a specific template instantiation.
|
|
// Friending to the generic template leads to a number of unintended consequences, including
|
|
// template resolution ambiguity and a circular dependency with Tracing.h.
|
|
T* unsafeUnbarrieredForTracing() { return &value; }
|
|
};
|
|
|
|
// Base class for barriered pointer types that intercept only writes.
|
|
template <class T>
|
|
class WriteBarrieredBase : public BarrieredBase<T>
|
|
{
|
|
protected:
|
|
// WriteBarrieredBase is not directly instantiable.
|
|
explicit WriteBarrieredBase(const T& v) : BarrieredBase<T>(v) {}
|
|
|
|
public:
|
|
DECLARE_POINTER_COMPARISON_OPS(T);
|
|
DECLARE_POINTER_CONSTREF_OPS(T);
|
|
|
|
// Use this if the automatic coercion to T isn't working.
|
|
const T& get() const { return this->value; }
|
|
|
|
// Use this if you want to change the value without invoking barriers.
|
|
// Obviously this is dangerous unless you know the barrier is not needed.
|
|
void unsafeSet(const T& v) { this->value = v; }
|
|
|
|
// For users who need to manually barrier the raw types.
|
|
static void writeBarrierPre(const T& v) { InternalBarrierMethods<T>::preBarrier(v); }
|
|
|
|
protected:
|
|
void pre() { InternalBarrierMethods<T>::preBarrier(this->value); }
|
|
void post(const T& prev, const T& next) {
|
|
InternalBarrierMethods<T>::postBarrier(&this->value, prev, next);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* PreBarriered only automatically handles pre-barriers. Post-barriers must be
|
|
* manually implemented when using this class. GCPtr and HeapPtr should be used
|
|
* in all cases that do not require explicit low-level control of moving
|
|
* behavior, e.g. for HashMap keys.
|
|
*/
|
|
template <class T>
|
|
class PreBarriered : public WriteBarrieredBase<T>
|
|
{
|
|
public:
|
|
PreBarriered() : WriteBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
|
|
/*
|
|
* Allow implicit construction for use in generic contexts, such as
|
|
* DebuggerWeakMap::markKeys.
|
|
*/
|
|
MOZ_IMPLICIT PreBarriered(const T& v) : WriteBarrieredBase<T>(v) {}
|
|
explicit PreBarriered(const PreBarriered<T>& v) : WriteBarrieredBase<T>(v.value) {}
|
|
~PreBarriered() { this->pre(); }
|
|
|
|
void init(const T& v) {
|
|
this->value = v;
|
|
}
|
|
|
|
/* Use to set the pointer to nullptr. */
|
|
void clear() {
|
|
this->pre();
|
|
this->value = nullptr;
|
|
}
|
|
|
|
DECLARE_POINTER_ASSIGN_OPS(PreBarriered, T);
|
|
|
|
private:
|
|
void set(const T& v) {
|
|
this->pre();
|
|
this->value = v;
|
|
}
|
|
};
|
|
|
|
/*
|
|
* A pre- and post-barriered heap pointer, for use inside the JS engine.
|
|
*
|
|
* It must only be stored in memory that has GC lifetime. GCPtr must not be
|
|
* used in contexts where it may be implicitly moved or deleted, e.g. most
|
|
* containers.
|
|
*
|
|
* The post-barriers implemented by this class are faster than those
|
|
* implemented by js::HeapPtr<T> or JS::Heap<T> at the cost of not
|
|
* automatically handling deletion or movement.
|
|
*/
|
|
template <class T>
|
|
class GCPtr : public WriteBarrieredBase<T>
|
|
{
|
|
public:
|
|
GCPtr() : WriteBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
|
|
explicit GCPtr(const T& v) : WriteBarrieredBase<T>(v) {
|
|
this->post(JS::GCPolicy<T>::initial(), v);
|
|
}
|
|
explicit GCPtr(const GCPtr<T>& v) : WriteBarrieredBase<T>(v) {
|
|
this->post(JS::GCPolicy<T>::initial(), v);
|
|
}
|
|
#ifdef DEBUG
|
|
~GCPtr() {
|
|
// No prebarrier necessary as this only happens when we are sweeping or
|
|
// after we have just collected the nursery. Note that the wrapped
|
|
// pointer may already have been freed by this point.
|
|
MOZ_ASSERT(CurrentThreadIsGCSweeping());
|
|
Poison(this, JS_FREED_HEAP_PTR_PATTERN, sizeof(*this));
|
|
}
|
|
#endif
|
|
|
|
void init(const T& v) {
|
|
this->value = v;
|
|
this->post(JS::GCPolicy<T>::initial(), v);
|
|
}
|
|
|
|
DECLARE_POINTER_ASSIGN_OPS(GCPtr, T);
|
|
|
|
T unbarrieredGet() const {
|
|
return this->value;
|
|
}
|
|
|
|
private:
|
|
void set(const T& v) {
|
|
this->pre();
|
|
T tmp = this->value;
|
|
this->value = v;
|
|
this->post(tmp, this->value);
|
|
}
|
|
|
|
/*
|
|
* Unlike HeapPtr<T>, GCPtr<T> must be managed with GC lifetimes.
|
|
* Specifically, the memory used by the pointer itself must be live until
|
|
* at least the next minor GC. For that reason, move semantics are invalid
|
|
* and are deleted here. Please note that not all containers support move
|
|
* semantics, so this does not completely prevent invalid uses.
|
|
*/
|
|
GCPtr(GCPtr<T>&&) = delete;
|
|
GCPtr<T>& operator=(GCPtr<T>&&) = delete;
|
|
};
|
|
|
|
/*
|
|
* A pre- and post-barriered heap pointer, for use inside the JS engine. These
|
|
* heap pointers can be stored in C++ containers like GCVector and GCHashMap.
|
|
*
|
|
* The GC sometimes keeps pointers to pointers to GC things --- for example, to
|
|
* track references into the nursery. However, C++ containers like GCVector and
|
|
* GCHashMap usually reserve the right to relocate their elements any time
|
|
* they're modified, invalidating all pointers to the elements. HeapPtr
|
|
* has a move constructor which knows how to keep the GC up to date if it is
|
|
* moved to a new location.
|
|
*
|
|
* However, because of this additional communication with the GC, HeapPtr
|
|
* is somewhat slower, so it should only be used in contexts where this ability
|
|
* is necessary.
|
|
*
|
|
* Obviously, JSObjects, JSStrings, and the like get tenured and compacted, so
|
|
* whatever pointers they contain get relocated, in the sense used here.
|
|
* However, since the GC itself is moving those values, it takes care of its
|
|
* internal pointers to those pointers itself. HeapPtr is only necessary
|
|
* when the relocation would otherwise occur without the GC's knowledge.
|
|
*/
|
|
template <class T>
|
|
class HeapPtr : public WriteBarrieredBase<T>
|
|
{
|
|
public:
|
|
HeapPtr() : WriteBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
|
|
|
|
// Implicitly adding barriers is a reasonable default.
|
|
MOZ_IMPLICIT HeapPtr(const T& v) : WriteBarrieredBase<T>(v) {
|
|
this->post(JS::GCPolicy<T>::initial(), this->value);
|
|
}
|
|
|
|
/*
|
|
* For HeapPtr, move semantics are equivalent to copy semantics. In
|
|
* C++, a copy constructor taking const-ref is the way to get a single
|
|
* function that will be used for both lvalue and rvalue copies, so we can
|
|
* simply omit the rvalue variant.
|
|
*/
|
|
MOZ_IMPLICIT HeapPtr(const HeapPtr<T>& v) : WriteBarrieredBase<T>(v) {
|
|
this->post(JS::GCPolicy<T>::initial(), this->value);
|
|
}
|
|
|
|
~HeapPtr() {
|
|
this->pre();
|
|
this->post(this->value, JS::GCPolicy<T>::initial());
|
|
}
|
|
|
|
void init(const T& v) {
|
|
this->value = v;
|
|
this->post(JS::GCPolicy<T>::initial(), this->value);
|
|
}
|
|
|
|
DECLARE_POINTER_ASSIGN_OPS(HeapPtr, T);
|
|
|
|
/* Make this friend so it can access pre() and post(). */
|
|
template <class T1, class T2>
|
|
friend inline void
|
|
BarrieredSetPair(Zone* zone,
|
|
HeapPtr<T1*>& v1, T1* val1,
|
|
HeapPtr<T2*>& v2, T2* val2);
|
|
|
|
protected:
|
|
void set(const T& v) {
|
|
this->pre();
|
|
postBarrieredSet(v);
|
|
}
|
|
|
|
void postBarrieredSet(const T& v) {
|
|
T tmp = this->value;
|
|
this->value = v;
|
|
this->post(tmp, this->value);
|
|
}
|
|
};
|
|
|
|
// Base class for barriered pointer types that intercept reads and writes.
|
|
template <typename T>
|
|
class ReadBarrieredBase : public BarrieredBase<T>
|
|
{
|
|
protected:
|
|
// ReadBarrieredBase is not directly instantiable.
|
|
explicit ReadBarrieredBase(const T& v) : BarrieredBase<T>(v) {}
|
|
|
|
protected:
|
|
void read() const { InternalBarrierMethods<T>::readBarrier(this->value); }
|
|
void post(const T& prev, const T& next) {
|
|
InternalBarrierMethods<T>::postBarrier(&this->value, prev, next);
|
|
}
|
|
};
|
|
|
|
// Incremental GC requires that weak pointers have read barriers. See the block
|
|
// comment at the top of Barrier.h for a complete discussion of why.
|
|
//
|
|
// Note that this class also has post-barriers, so is safe to use with nursery
|
|
// pointers. However, when used as a hashtable key, care must still be taken to
|
|
// insert manual post-barriers on the table for rekeying if the key is based in
|
|
// any way on the address of the object.
|
|
template <typename T>
|
|
class ReadBarriered : public ReadBarrieredBase<T>
|
|
{
|
|
public:
|
|
ReadBarriered() : ReadBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
|
|
|
|
// It is okay to add barriers implicitly.
|
|
MOZ_IMPLICIT ReadBarriered(const T& v) : ReadBarrieredBase<T>(v) {
|
|
this->post(JS::GCPolicy<T>::initial(), v);
|
|
}
|
|
|
|
// Copy is creating a new edge, so we must read barrier the source edge.
|
|
explicit ReadBarriered(const ReadBarriered& v) : ReadBarrieredBase<T>(v) {
|
|
this->post(JS::GCPolicy<T>::initial(), v.get());
|
|
}
|
|
|
|
// Move retains the lifetime status of the source edge, so does not fire
|
|
// the read barrier of the defunct edge.
|
|
ReadBarriered(ReadBarriered&& v)
|
|
: ReadBarrieredBase<T>(mozilla::Move(v))
|
|
{
|
|
this->post(JS::GCPolicy<T>::initial(), v.value);
|
|
}
|
|
|
|
~ReadBarriered() {
|
|
this->post(this->value, JS::GCPolicy<T>::initial());
|
|
}
|
|
|
|
ReadBarriered& operator=(const ReadBarriered& v) {
|
|
T prior = this->value;
|
|
this->value = v.value;
|
|
this->post(prior, v.value);
|
|
return *this;
|
|
}
|
|
|
|
const T get() const {
|
|
if (!InternalBarrierMethods<T>::isMarkable(this->value))
|
|
return JS::GCPolicy<T>::initial();
|
|
this->read();
|
|
return this->value;
|
|
}
|
|
|
|
const T unbarrieredGet() const {
|
|
return this->value;
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
return bool(this->value);
|
|
}
|
|
|
|
operator const T() const { return get(); }
|
|
|
|
const T operator->() const { return get(); }
|
|
|
|
T* unsafeGet() { return &this->value; }
|
|
T const* unsafeGet() const { return &this->value; }
|
|
|
|
void set(const T& v)
|
|
{
|
|
T tmp = this->value;
|
|
this->value = v;
|
|
this->post(tmp, v);
|
|
}
|
|
};
|
|
|
|
// A WeakRef pointer does not hold its target live and is automatically nulled
|
|
// out when the GC discovers that it is not reachable from any other path.
|
|
template <typename T>
|
|
using WeakRef = ReadBarriered<T>;
|
|
|
|
// Add Value operations to all Barrier types. Note, this must be defined before
|
|
// HeapSlot for HeapSlot's base to get these operations.
|
|
template <>
|
|
class BarrieredBaseMixins<JS::Value> : public ValueOperations<WriteBarrieredBase<JS::Value>>
|
|
{};
|
|
|
|
// A pre- and post-barriered Value that is specialized to be aware that it
|
|
// resides in a slots or elements vector. This allows it to be relocated in
|
|
// memory, but with substantially less overhead than a HeapPtr.
|
|
class HeapSlot : public WriteBarrieredBase<Value>
|
|
{
|
|
public:
|
|
enum Kind {
|
|
Slot = 0,
|
|
Element = 1
|
|
};
|
|
|
|
void init(NativeObject* owner, Kind kind, uint32_t slot, const Value& v) {
|
|
value = v;
|
|
post(owner, kind, slot, v);
|
|
}
|
|
|
|
void destroy() {
|
|
pre();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool preconditionForSet(NativeObject* owner, Kind kind, uint32_t slot) const;
|
|
bool preconditionForWriteBarrierPost(NativeObject* obj, Kind kind, uint32_t slot,
|
|
const Value& target) const;
|
|
#endif
|
|
|
|
void set(NativeObject* owner, Kind kind, uint32_t slot, const Value& v) {
|
|
MOZ_ASSERT(preconditionForSet(owner, kind, slot));
|
|
pre();
|
|
value = v;
|
|
post(owner, kind, slot, v);
|
|
}
|
|
|
|
private:
|
|
void post(NativeObject* owner, Kind kind, uint32_t slot, const Value& target) {
|
|
MOZ_ASSERT(preconditionForWriteBarrierPost(owner, kind, slot, target));
|
|
if (this->value.isObject()) {
|
|
gc::Cell* cell = reinterpret_cast<gc::Cell*>(&this->value.toObject());
|
|
if (cell->storeBuffer())
|
|
cell->storeBuffer()->putSlot(owner, kind, slot, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
class HeapSlotArray
|
|
{
|
|
HeapSlot* array;
|
|
|
|
// Whether writes may be performed to the slots in this array. This helps
|
|
// to control how object elements which may be copy on write are used.
|
|
#ifdef DEBUG
|
|
bool allowWrite_;
|
|
#endif
|
|
|
|
public:
|
|
explicit HeapSlotArray(HeapSlot* array, bool allowWrite)
|
|
: array(array)
|
|
#ifdef DEBUG
|
|
, allowWrite_(allowWrite)
|
|
#endif
|
|
{}
|
|
|
|
operator const Value*() const {
|
|
JS_STATIC_ASSERT(sizeof(GCPtr<Value>) == sizeof(Value));
|
|
JS_STATIC_ASSERT(sizeof(HeapSlot) == sizeof(Value));
|
|
return reinterpret_cast<const Value*>(array);
|
|
}
|
|
operator HeapSlot*() const { MOZ_ASSERT(allowWrite()); return array; }
|
|
|
|
HeapSlotArray operator +(int offset) const { return HeapSlotArray(array + offset, allowWrite()); }
|
|
HeapSlotArray operator +(uint32_t offset) const { return HeapSlotArray(array + offset, allowWrite()); }
|
|
|
|
private:
|
|
bool allowWrite() const {
|
|
#ifdef DEBUG
|
|
return allowWrite_;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
/*
|
|
* This is a hack for RegExpStatics::updateFromMatch. It allows us to do two
|
|
* barriers with only one branch to check if we're in an incremental GC.
|
|
*/
|
|
template <class T1, class T2>
|
|
static inline void
|
|
BarrieredSetPair(Zone* zone,
|
|
HeapPtr<T1*>& v1, T1* val1,
|
|
HeapPtr<T2*>& v2, T2* val2)
|
|
{
|
|
if (T1::needWriteBarrierPre(zone)) {
|
|
v1.pre();
|
|
v2.pre();
|
|
}
|
|
v1.postBarrieredSet(val1);
|
|
v2.postBarrieredSet(val2);
|
|
}
|
|
|
|
/*
|
|
* ImmutableTenuredPtr is designed for one very narrow case: replacing
|
|
* immutable raw pointers to GC-managed things, implicitly converting to a
|
|
* handle type for ease of use. Pointers encapsulated by this type must:
|
|
*
|
|
* be immutable (no incremental write barriers),
|
|
* never point into the nursery (no generational write barriers), and
|
|
* be traced via MarkRuntime (we use fromMarkedLocation).
|
|
*
|
|
* In short: you *really* need to know what you're doing before you use this
|
|
* class!
|
|
*/
|
|
template <typename T>
|
|
class ImmutableTenuredPtr
|
|
{
|
|
T value;
|
|
|
|
public:
|
|
operator T() const { return value; }
|
|
T operator->() const { return value; }
|
|
|
|
operator Handle<T>() const {
|
|
return Handle<T>::fromMarkedLocation(&value);
|
|
}
|
|
|
|
void init(T ptr) {
|
|
MOZ_ASSERT(ptr->isTenured());
|
|
value = ptr;
|
|
}
|
|
|
|
T get() const { return value; }
|
|
const T* address() { return &value; }
|
|
};
|
|
|
|
template <typename T>
|
|
struct MovableCellHasher<PreBarriered<T>>
|
|
{
|
|
using Key = PreBarriered<T>;
|
|
using Lookup = T;
|
|
|
|
static bool hasHash(const Lookup& l) { return MovableCellHasher<T>::hasHash(l); }
|
|
static bool ensureHash(const Lookup& l) { return MovableCellHasher<T>::ensureHash(l); }
|
|
static HashNumber hash(const Lookup& l) { return MovableCellHasher<T>::hash(l); }
|
|
static bool match(const Key& k, const Lookup& l) { return MovableCellHasher<T>::match(k, l); }
|
|
static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); }
|
|
};
|
|
|
|
template <typename T>
|
|
struct MovableCellHasher<HeapPtr<T>>
|
|
{
|
|
using Key = HeapPtr<T>;
|
|
using Lookup = T;
|
|
|
|
static bool hasHash(const Lookup& l) { return MovableCellHasher<T>::hasHash(l); }
|
|
static bool ensureHash(const Lookup& l) { return MovableCellHasher<T>::ensureHash(l); }
|
|
static HashNumber hash(const Lookup& l) { return MovableCellHasher<T>::hash(l); }
|
|
static bool match(const Key& k, const Lookup& l) { return MovableCellHasher<T>::match(k, l); }
|
|
static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); }
|
|
};
|
|
|
|
template <typename T>
|
|
struct MovableCellHasher<ReadBarriered<T>>
|
|
{
|
|
using Key = ReadBarriered<T>;
|
|
using Lookup = T;
|
|
|
|
static bool hasHash(const Lookup& l) { return MovableCellHasher<T>::hasHash(l); }
|
|
static bool ensureHash(const Lookup& l) { return MovableCellHasher<T>::ensureHash(l); }
|
|
static HashNumber hash(const Lookup& l) { return MovableCellHasher<T>::hash(l); }
|
|
static bool match(const Key& k, const Lookup& l) {
|
|
return MovableCellHasher<T>::match(k.unbarrieredGet(), l);
|
|
}
|
|
static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); }
|
|
};
|
|
|
|
/* Useful for hashtables with a GCPtr as key. */
|
|
template <class T>
|
|
struct GCPtrHasher
|
|
{
|
|
typedef GCPtr<T> Key;
|
|
typedef T Lookup;
|
|
|
|
static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); }
|
|
static bool match(const Key& k, Lookup l) { return k.get() == l; }
|
|
static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); }
|
|
};
|
|
|
|
/* Specialized hashing policy for GCPtrs. */
|
|
template <class T>
|
|
struct DefaultHasher<GCPtr<T>> : GCPtrHasher<T> {};
|
|
|
|
template <class T>
|
|
struct PreBarrieredHasher
|
|
{
|
|
typedef PreBarriered<T> Key;
|
|
typedef T Lookup;
|
|
|
|
static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); }
|
|
static bool match(const Key& k, Lookup l) { return k.get() == l; }
|
|
static void rekey(Key& k, const Key& newKey) { k.unsafeSet(newKey); }
|
|
};
|
|
|
|
template <class T>
|
|
struct DefaultHasher<PreBarriered<T>> : PreBarrieredHasher<T> { };
|
|
|
|
/* Useful for hashtables with a ReadBarriered as key. */
|
|
template <class T>
|
|
struct ReadBarrieredHasher
|
|
{
|
|
typedef ReadBarriered<T> Key;
|
|
typedef T Lookup;
|
|
|
|
static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); }
|
|
static bool match(const Key& k, Lookup l) { return k.unbarrieredGet() == l; }
|
|
static void rekey(Key& k, const Key& newKey) { k.set(newKey.unbarrieredGet()); }
|
|
};
|
|
|
|
/* Specialized hashing policy for ReadBarriereds. */
|
|
template <class T>
|
|
struct DefaultHasher<ReadBarriered<T>> : ReadBarrieredHasher<T> { };
|
|
|
|
class ArrayObject;
|
|
class ArrayBufferObject;
|
|
class GlobalObject;
|
|
class Scope;
|
|
class ScriptSourceObject;
|
|
class Shape;
|
|
class BaseShape;
|
|
class UnownedBaseShape;
|
|
class WasmInstanceObject;
|
|
class WasmTableObject;
|
|
namespace jit {
|
|
class JitCode;
|
|
} // namespace jit
|
|
|
|
typedef PreBarriered<JSObject*> PreBarrieredObject;
|
|
typedef PreBarriered<JSScript*> PreBarrieredScript;
|
|
typedef PreBarriered<jit::JitCode*> PreBarrieredJitCode;
|
|
typedef PreBarriered<JSString*> PreBarrieredString;
|
|
typedef PreBarriered<JSAtom*> PreBarrieredAtom;
|
|
|
|
typedef GCPtr<NativeObject*> GCPtrNativeObject;
|
|
typedef GCPtr<ArrayObject*> GCPtrArrayObject;
|
|
typedef GCPtr<ArrayBufferObjectMaybeShared*> GCPtrArrayBufferObjectMaybeShared;
|
|
typedef GCPtr<ArrayBufferObject*> GCPtrArrayBufferObject;
|
|
typedef GCPtr<BaseShape*> GCPtrBaseShape;
|
|
typedef GCPtr<JSAtom*> GCPtrAtom;
|
|
typedef GCPtr<JSFlatString*> GCPtrFlatString;
|
|
typedef GCPtr<JSFunction*> GCPtrFunction;
|
|
typedef GCPtr<JSLinearString*> GCPtrLinearString;
|
|
typedef GCPtr<JSObject*> GCPtrObject;
|
|
typedef GCPtr<JSScript*> GCPtrScript;
|
|
typedef GCPtr<JSString*> GCPtrString;
|
|
typedef GCPtr<ModuleObject*> GCPtrModuleObject;
|
|
typedef GCPtr<ModuleEnvironmentObject*> GCPtrModuleEnvironmentObject;
|
|
typedef GCPtr<ModuleNamespaceObject*> GCPtrModuleNamespaceObject;
|
|
typedef GCPtr<PlainObject*> GCPtrPlainObject;
|
|
typedef GCPtr<PropertyName*> GCPtrPropertyName;
|
|
typedef GCPtr<Shape*> GCPtrShape;
|
|
typedef GCPtr<UnownedBaseShape*> GCPtrUnownedBaseShape;
|
|
typedef GCPtr<jit::JitCode*> GCPtrJitCode;
|
|
typedef GCPtr<ObjectGroup*> GCPtrObjectGroup;
|
|
typedef GCPtr<Scope*> GCPtrScope;
|
|
|
|
typedef PreBarriered<Value> PreBarrieredValue;
|
|
typedef GCPtr<Value> GCPtrValue;
|
|
|
|
typedef PreBarriered<jsid> PreBarrieredId;
|
|
typedef GCPtr<jsid> GCPtrId;
|
|
|
|
typedef ImmutableTenuredPtr<PropertyName*> ImmutablePropertyNamePtr;
|
|
typedef ImmutableTenuredPtr<JS::Symbol*> ImmutableSymbolPtr;
|
|
|
|
typedef ReadBarriered<DebugEnvironmentProxy*> ReadBarrieredDebugEnvironmentProxy;
|
|
typedef ReadBarriered<GlobalObject*> ReadBarrieredGlobalObject;
|
|
typedef ReadBarriered<JSObject*> ReadBarrieredObject;
|
|
typedef ReadBarriered<JSFunction*> ReadBarrieredFunction;
|
|
typedef ReadBarriered<JSScript*> ReadBarrieredScript;
|
|
typedef ReadBarriered<ScriptSourceObject*> ReadBarrieredScriptSourceObject;
|
|
typedef ReadBarriered<Shape*> ReadBarrieredShape;
|
|
typedef ReadBarriered<jit::JitCode*> ReadBarrieredJitCode;
|
|
typedef ReadBarriered<ObjectGroup*> ReadBarrieredObjectGroup;
|
|
typedef ReadBarriered<JS::Symbol*> ReadBarrieredSymbol;
|
|
typedef ReadBarriered<WasmInstanceObject*> ReadBarrieredWasmInstanceObject;
|
|
typedef ReadBarriered<WasmTableObject*> ReadBarrieredWasmTableObject;
|
|
|
|
typedef ReadBarriered<Value> ReadBarrieredValue;
|
|
|
|
} /* namespace js */
|
|
|
|
#endif /* gc_Barrier_h */
|