xo-alloc: mutation log tracking in working state + unit test
This commit is contained in:
parent
5f46b51f12
commit
c7488cbfd5
14 changed files with 659 additions and 94 deletions
|
|
@ -12,3 +12,6 @@ xo-alloc provides arena allocators and a generation garbage collector
|
||||||
install
|
install
|
||||||
introduction
|
introduction
|
||||||
implementation
|
implementation
|
||||||
|
glossary
|
||||||
|
genindex
|
||||||
|
search
|
||||||
|
|
|
||||||
|
|
@ -45,24 +45,24 @@ namespace xo {
|
||||||
std::size_t z,
|
std::size_t z,
|
||||||
bool debug_flag);
|
bool debug_flag);
|
||||||
|
|
||||||
const std::string & name() const { return name_; }
|
|
||||||
std::byte * free_ptr() const { return free_ptr_; }
|
std::byte * free_ptr() const { return free_ptr_; }
|
||||||
void set_free_ptr(std::byte * x);
|
void set_free_ptr(std::byte * x);
|
||||||
|
|
||||||
// inherited from IAlloc...
|
// inherited from IAlloc...
|
||||||
|
|
||||||
virtual std::size_t size() const override;
|
virtual const std::string & name() const final override { return name_; }
|
||||||
virtual std::size_t available() const override;
|
virtual std::size_t size() const final override;
|
||||||
virtual std::size_t allocated() const override;
|
virtual std::size_t available() const final override;
|
||||||
virtual bool contains(const void * x) const override;
|
virtual std::size_t allocated() const final override;
|
||||||
virtual bool is_before_checkpoint(const void * x) const override;
|
virtual bool contains(const void * x) const final override;
|
||||||
virtual std::size_t before_checkpoint() const override;
|
virtual bool is_before_checkpoint(const void * x) const final override;
|
||||||
virtual std::size_t after_checkpoint() const override;
|
virtual std::size_t before_checkpoint() const final override;
|
||||||
|
virtual std::size_t after_checkpoint() const final override;
|
||||||
|
|
||||||
virtual void clear() override;
|
virtual void clear() final override;
|
||||||
virtual void checkpoint() override;
|
virtual void checkpoint() final override;
|
||||||
virtual std::byte * alloc(std::size_t z) override;
|
virtual std::byte * alloc(std::size_t z) final override;
|
||||||
virtual void release_redline_memory() override;
|
virtual void release_redline_memory() final override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ArenaAlloc(const std::string & name, std::size_t rz, std::size_t z, bool debug_flag);
|
ArenaAlloc(const std::string & name, std::size_t rz, std::size_t z, bool debug_flag);
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ namespace xo {
|
||||||
explicit Forwarding1(gp<Object> dest);
|
explicit Forwarding1(gp<Object> dest);
|
||||||
|
|
||||||
// inherited from Object..
|
// inherited from Object..
|
||||||
virtual bool _is_forwarded() const override { return true; }
|
virtual bool _is_forwarded() const final override { return true; }
|
||||||
virtual Object * _offset_destination(Object * src) const;
|
virtual Object * _offset_destination(Object * src) const final override;
|
||||||
virtual std::size_t _shallow_size() const override;
|
virtual Object * _destination() final override;
|
||||||
virtual Object * _shallow_copy() const override;
|
/** never called on Forwarding1 **/
|
||||||
virtual std::size_t _forward_children() override;
|
virtual std::size_t _shallow_size() const final override;
|
||||||
|
/** never called on Forwarding1 **/
|
||||||
|
virtual Object * _shallow_copy() const final override;
|
||||||
|
/** never called on Forwarding1 **/
|
||||||
|
virtual std::size_t _forward_children() final override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** the object that used to be located at this address (i.e. @c this)
|
/** the object that used to be located at this address (i.e. @c this)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ namespace xo {
|
||||||
|
|
||||||
constexpr std::size_t gen2int(generation x) { return static_cast<std::size_t>(x); }
|
constexpr std::size_t gen2int(generation x) { return static_cast<std::size_t>(x); }
|
||||||
|
|
||||||
|
enum class generation_result {
|
||||||
|
nursery,
|
||||||
|
tenured,
|
||||||
|
not_found
|
||||||
|
};
|
||||||
|
|
||||||
enum class role {
|
enum class role {
|
||||||
/** nursery: generation for new objects **/
|
/** nursery: generation for new objects **/
|
||||||
from_space,
|
from_space,
|
||||||
|
|
@ -131,6 +137,17 @@ namespace xo {
|
||||||
/** total bytes promoted from nursery->tenured since inception **/
|
/** total bytes promoted from nursery->tenured since inception **/
|
||||||
std::size_t total_promoted_ = 0;
|
std::size_t total_promoted_ = 0;
|
||||||
|
|
||||||
|
/** total number of mutations to already-allocated objects,
|
||||||
|
* whether or not GC needs to log them.
|
||||||
|
**/
|
||||||
|
std::size_t n_mutation_ = 0;
|
||||||
|
/** total number of mutation eligible for logging **/
|
||||||
|
std::size_t n_logged_mutation_ = 0;
|
||||||
|
/** total number of cross-generation mutations (tenured->nursery when reported) **/
|
||||||
|
std::size_t n_xgen_mutation_ = 0;
|
||||||
|
/** total number of cross-checkpoint mutations (N0 -> N1 when reported) **/
|
||||||
|
std::size_t n_xckp_mutation_ = 0;
|
||||||
|
|
||||||
/** per-type statistics (placeholder) **/
|
/** per-type statistics (placeholder) **/
|
||||||
ObjectStatistics per_type_stats_;
|
ObjectStatistics per_type_stats_;
|
||||||
};
|
};
|
||||||
|
|
@ -163,6 +180,35 @@ namespace xo {
|
||||||
bool full_move_ = false;
|
bool full_move_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MutationLogEntry {
|
||||||
|
public:
|
||||||
|
MutationLogEntry(Object * parent, Object ** lhs) : parent_{parent}, lhs_{lhs} {}
|
||||||
|
|
||||||
|
Object * parent() const { return parent_; }
|
||||||
|
Object ** lhs() const { return lhs_; }
|
||||||
|
|
||||||
|
Object * child() const { return *lhs_; }
|
||||||
|
|
||||||
|
bool is_child_forwarded() const;
|
||||||
|
bool is_parent_forwarded() const;
|
||||||
|
|
||||||
|
Object * parent_destination() const;
|
||||||
|
|
||||||
|
/** Flag obsolete mutation.
|
||||||
|
* Future proofing, never happens for regular objects
|
||||||
|
**/
|
||||||
|
bool is_dead() const { return false; }
|
||||||
|
|
||||||
|
MutationLogEntry update_parent_moved(Object * parent_to) const;
|
||||||
|
void fixup_parent_child_moved(Object * child_to) { *lhs_ = child_to; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Object * parent_;
|
||||||
|
Object ** lhs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MutationLog = std::vector<MutationLogEntry>;
|
||||||
|
|
||||||
/** @class GC
|
/** @class GC
|
||||||
* @brief generational garbage collector
|
* @brief generational garbage collector
|
||||||
*
|
*
|
||||||
|
|
@ -185,16 +231,18 @@ namespace xo {
|
||||||
|
|
||||||
/** true iff GC permitted in current state **/
|
/** true iff GC permitted in current state **/
|
||||||
bool is_gc_enabled() const { return gc_enabled_ == 0; }
|
bool is_gc_enabled() const { return gc_enabled_ == 0; }
|
||||||
/** @return generation to which object at @p x belongs **/
|
|
||||||
generation generation_of(const void * x) const;
|
|
||||||
/** @return generation that contains @p x, given it's in from-space **/
|
|
||||||
generation fromspace_generation_of(const void * x) const;
|
|
||||||
/** true iff from-space contains @p x **/
|
|
||||||
bool fromspace_contains(const void * x) const;
|
|
||||||
/** true during (and only during) a GC cycle **/
|
/** true during (and only during) a GC cycle **/
|
||||||
bool gc_in_progress() const { return runstate_.in_progress(); }
|
bool gc_in_progress() const { return runstate_.in_progress(); }
|
||||||
/** return free pointer for generation @p gen, i.e. nursery or tenured space **/
|
/** @return generation to which object at @p x belongs **/
|
||||||
|
generation_result tospace_generation_of(const void * x) const;
|
||||||
|
/** @return generation that contains @p x, given it's in from-space **/
|
||||||
|
generation_result fromspace_generation_of(const void * x) const;
|
||||||
|
/** true iff from-space contains @p x **/
|
||||||
|
bool fromspace_contains(const void * x) const;
|
||||||
|
/** @return free pointer for generation @p gen, i.e. nursery or tenured space **/
|
||||||
std::byte * free_ptr(generation gen);
|
std::byte * free_ptr(generation gen);
|
||||||
|
/** @return current size of (number of entries in) mutation log **/
|
||||||
|
std::size_t mlog_size() const;
|
||||||
|
|
||||||
/** add gc root at address @p addr . Gc will keep alive anything reachable
|
/** add gc root at address @p addr . Gc will keep alive anything reachable
|
||||||
* from @c *addr
|
* from @c *addr
|
||||||
|
|
@ -217,27 +265,43 @@ namespace xo {
|
||||||
|
|
||||||
// inherited from IAlloc..
|
// inherited from IAlloc..
|
||||||
|
|
||||||
|
virtual const std::string & name() const final override;
|
||||||
/** capacity in bytes (counting both free+allocated) for object storage.
|
/** capacity in bytes (counting both free+allocated) for object storage.
|
||||||
* only counts one of {to-space, from-space},
|
* only counts one of {to-space, from-space},
|
||||||
* since one role is always held empty between collections.
|
* since one role is always held empty between collections.
|
||||||
**/
|
**/
|
||||||
virtual std::size_t size() const override;
|
virtual std::size_t size() const final override;
|
||||||
|
|
||||||
virtual std::size_t allocated() const override;
|
virtual std::size_t allocated() const final override;
|
||||||
virtual std::size_t available() const override;
|
virtual std::size_t available() const final override;
|
||||||
/** only tests to-space **/
|
/** only tests to-space **/
|
||||||
virtual bool contains(const void * x) const override;
|
virtual bool contains(const void * x) const final override;
|
||||||
virtual bool is_before_checkpoint(const void * x) const override;
|
virtual bool is_before_checkpoint(const void * x) const final override;
|
||||||
virtual std::size_t before_checkpoint() const override;
|
virtual std::size_t before_checkpoint() const final override;
|
||||||
virtual std::size_t after_checkpoint() const override;
|
virtual std::size_t after_checkpoint() const final override;
|
||||||
|
virtual bool debug_flag() const final override;
|
||||||
|
|
||||||
virtual void clear() override;
|
virtual void clear() final override;
|
||||||
virtual void checkpoint() override;
|
virtual void checkpoint() final override;
|
||||||
|
|
||||||
virtual std::byte * alloc(std::size_t z) override;
|
/** GC bookkeeping for an assignment that modifes an Object reference.
|
||||||
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) override;
|
* Whenever an @ref Object instance P contains a member variable that can refer
|
||||||
|
* to another @ref Object, then we need to involve GC to perform the assignment.
|
||||||
|
* In particular a side-effect that changes the target of such reference to Q after P
|
||||||
|
* has been promoted, may lead to a tenured->nursery cross-generational pointer.
|
||||||
|
* GC needs to know about such pointers to it can update them as part of subsequent
|
||||||
|
* incremental collections.
|
||||||
|
*
|
||||||
|
* @param parent. object with member variable being modified
|
||||||
|
* @param lhs. address of a member variable within the allocation of @p parent.
|
||||||
|
* @param rhs. new target for @p *lhs
|
||||||
|
**/
|
||||||
|
virtual void assign_member(Object * parent, Object ** lhs, Object* rhs) final override;
|
||||||
|
|
||||||
virtual void release_redline_memory() override;
|
virtual std::byte * alloc(std::size_t z) final override;
|
||||||
|
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override;
|
||||||
|
|
||||||
|
virtual void release_redline_memory() final override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** begin GC now **/
|
/** begin GC now **/
|
||||||
|
|
@ -248,12 +312,35 @@ namespace xo {
|
||||||
void swap_nursery();
|
void swap_nursery();
|
||||||
/** swap roles of From/To spaces for tenured generation **/
|
/** swap roles of From/To spaces for tenured generation **/
|
||||||
void swap_tenured();
|
void swap_tenured();
|
||||||
|
/** swap roles of From/To spaces for mutation log **/
|
||||||
|
void swap_mutation_log();
|
||||||
/** swap roles of FromSpace/ToSpace **/
|
/** swap roles of FromSpace/ToSpace **/
|
||||||
void swap_spaces(generation g);
|
void swap_spaces(generation g);
|
||||||
/** copy object **/
|
/** copy object **/
|
||||||
void copy_object(Object ** addr, generation upto, ObjectStatistics * object_stats);
|
void copy_object(Object ** addr, generation upto, ObjectStatistics * object_stats);
|
||||||
/** copy everything reachable from global gc roots **/
|
/** copy everything reachable from global gc roots **/
|
||||||
void copy_globals(generation g);
|
void copy_globals(generation g);
|
||||||
|
/** review mutation log; may discover+rescue reachable objects.
|
||||||
|
**/
|
||||||
|
void forward_mutation_log(generation upto);
|
||||||
|
/** Aux function for @ref execute_gc. Updates bookkeeping for cross-generational
|
||||||
|
* (T->N, aka xgen) and (N1->N0, aka xckp) pointers
|
||||||
|
**/
|
||||||
|
void incremental_gc_forward_mlog(ObjectStatistics * per_type_stats);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aux function for @ref incremental_gc_forward_mlog. Calls this function until
|
||||||
|
* fixpoint.
|
||||||
|
*
|
||||||
|
* @param from_mlog incoming mutation log. Contains {xgen,xckp} pointers before GC.
|
||||||
|
* Contents of this log is consumed (+discarded) before method returns.
|
||||||
|
* @param to_mlog outgoing mutation log. Will contain {xgen,xckp} pointers after GC.
|
||||||
|
* @param defer_mlog contains log entries associated with possible garbage.
|
||||||
|
**/
|
||||||
|
void incremental_gc_forward_mlog_phase(MutationLog * from_mlog,
|
||||||
|
MutationLog * to_mlog,
|
||||||
|
MutationLog * defer_mlog,
|
||||||
|
ObjectStatistics * per_type_stats);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** garbage collector configuration **/
|
/** garbage collector configuration **/
|
||||||
|
|
@ -262,11 +349,11 @@ namespace xo {
|
||||||
/** contains allocated objects, along with unreachable garbage to be collected.
|
/** contains allocated objects, along with unreachable garbage to be collected.
|
||||||
* roles reverse after each incremental, or full, collection.
|
* roles reverse after each incremental, or full, collection.
|
||||||
**/
|
**/
|
||||||
std::array<up<ListAlloc>, static_cast<std::size_t>(role::N)> nursery_;
|
std::array<up<ListAlloc>, role2int(role::N)> nursery_;
|
||||||
/** empty space, destination for objects that survive collection.
|
/** empty space, destination for objects that survive collection.
|
||||||
* roles reverse after each full collection.
|
* roles reverse after each full collection.
|
||||||
**/
|
**/
|
||||||
std::array<up<ListAlloc>, static_cast<std::size_t>(role::N)> tenured_;
|
std::array<up<ListAlloc>, role2int(role::N)> tenured_;
|
||||||
|
|
||||||
/** current state of GC activity.
|
/** current state of GC activity.
|
||||||
* @text
|
* @text
|
||||||
|
|
@ -286,6 +373,13 @@ namespace xo {
|
||||||
**/
|
**/
|
||||||
std::vector<Object**> gc_root_v_;
|
std::vector<Object**> gc_root_v_;
|
||||||
|
|
||||||
|
/** log cross-generational and cross-checkpoint mutations.
|
||||||
|
* These need to be adjusted on next incremental collection
|
||||||
|
**/
|
||||||
|
std::array<up<MutationLog>, role2int(role::N)> mutation_log_;
|
||||||
|
/** temporary mutation log (for deferred entries) **/
|
||||||
|
up<MutationLog> defer_mutation_log_;
|
||||||
|
|
||||||
/** allocation/collection counters **/
|
/** allocation/collection counters **/
|
||||||
GcStatistics gc_statistics_;
|
GcStatistics gc_statistics_;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ namespace xo {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using up = std::unique_ptr<T>;
|
using up = std::unique_ptr<T>;
|
||||||
|
|
||||||
|
class Object;
|
||||||
|
|
||||||
namespace gc {
|
namespace gc {
|
||||||
/** @class IAllocator
|
/** @class IAllocator
|
||||||
* @brief memory allocation interface with limited garbaga collector support
|
* @brief memory allocation interface with limited garbaga collector support
|
||||||
|
|
@ -27,6 +29,8 @@ namespace xo {
|
||||||
/** z + alloc_padding(z) **/
|
/** z + alloc_padding(z) **/
|
||||||
static std::size_t with_padding(std::size_t z);
|
static std::size_t with_padding(std::size_t z);
|
||||||
|
|
||||||
|
/** optional name for this allocator; labelling for diagnostics **/
|
||||||
|
virtual const std::string & name() const = 0;
|
||||||
/** allocator size in bytes (up to soft limit).
|
/** allocator size in bytes (up to soft limit).
|
||||||
* Includes unallocated mmeory
|
* Includes unallocated mmeory
|
||||||
**/
|
**/
|
||||||
|
|
@ -47,15 +51,25 @@ namespace xo {
|
||||||
virtual std::size_t before_checkpoint() const = 0;
|
virtual std::size_t before_checkpoint() const = 0;
|
||||||
/** number of bytes allocated since @ref checkpoint **/
|
/** number of bytes allocated since @ref checkpoint **/
|
||||||
virtual std::size_t after_checkpoint() const = 0;
|
virtual std::size_t after_checkpoint() const = 0;
|
||||||
|
/** @return true iff debug logging enabled **/
|
||||||
|
virtual bool debug_flag() const { return false; }
|
||||||
|
|
||||||
/** reset allocator to empty state. **/
|
/** reset allocator to empty state. **/
|
||||||
virtual void clear() = 0;
|
virtual void clear() = 0;
|
||||||
/** remember allocator state. All currently-allocated addresses x
|
/** remember allocator state. All currently-allocated addresses xo
|
||||||
* will satisfy is_before_checkpoint(x). Subsequent allocations x
|
* will satisfy is_before_checkpoint(x). Subsequent allocations x
|
||||||
* will fail is_before_checkpoint(x), until checkpoint superseded
|
* will fail is_before_checkpoint(x), until checkpoint superseded
|
||||||
* by @ref clear or another call to @ref checkpoint
|
* by @ref clear or another call to @ref checkpoint
|
||||||
**/
|
**/
|
||||||
virtual void checkpoint() = 0;
|
virtual void checkpoint() = 0;
|
||||||
|
/** perform assignment
|
||||||
|
* @code
|
||||||
|
* *lhs = rhs
|
||||||
|
* @endcode
|
||||||
|
* plus additional book keeping if needed (e.g. in @ref GC)
|
||||||
|
* Default implementation just does the assignment.
|
||||||
|
**/
|
||||||
|
virtual void assign_member(Object * parent, Object ** lhs, Object * rhs);
|
||||||
/** allocate @p z bytes of memory. returns pointer to first address **/
|
/** allocate @p z bytes of memory. returns pointer to first address **/
|
||||||
virtual std::byte * alloc(std::size_t z) = 0;
|
virtual std::byte * alloc(std::size_t z) = 0;
|
||||||
/** allocate @p z bytes for copy of object at @p src.
|
/** allocate @p z bytes for copy of object at @p src.
|
||||||
|
|
|
||||||
|
|
@ -45,18 +45,19 @@ namespace xo {
|
||||||
|
|
||||||
// inherited from IAlloc..
|
// inherited from IAlloc..
|
||||||
|
|
||||||
virtual std::size_t size() const override;
|
virtual const std::string & name() const final override;
|
||||||
virtual std::size_t available() const override;
|
virtual std::size_t size() const final override;
|
||||||
virtual std::size_t allocated() const override;
|
virtual std::size_t available() const final override;
|
||||||
virtual bool contains(const void * x) const override;
|
virtual std::size_t allocated() const final override;
|
||||||
virtual bool is_before_checkpoint(const void * x) const override;
|
virtual bool contains(const void * x) const final override;
|
||||||
virtual std::size_t before_checkpoint() const override;
|
virtual bool is_before_checkpoint(const void * x) const final override;
|
||||||
virtual std::size_t after_checkpoint() const override;
|
virtual std::size_t before_checkpoint() const final override;
|
||||||
|
virtual std::size_t after_checkpoint() const final override;
|
||||||
|
|
||||||
virtual void clear() override;
|
virtual void clear() final override;
|
||||||
virtual void checkpoint() override;
|
virtual void checkpoint() final override;
|
||||||
virtual std::byte * alloc(std::size_t z) override;
|
virtual std::byte * alloc(std::size_t z) final override;
|
||||||
virtual void release_redline_memory() override;
|
virtual void release_redline_memory() final override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** **/
|
/** **/
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ namespace xo {
|
||||||
class ObjectStatistics;
|
class ObjectStatistics;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Object;
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class gc_ptr;
|
class gc_ptr;
|
||||||
|
|
||||||
|
|
@ -88,6 +86,13 @@ namespace xo {
|
||||||
**/
|
**/
|
||||||
static gc::IAlloc * mm;
|
static gc::IAlloc * mm;
|
||||||
|
|
||||||
|
/** assign value @p rhs to member @p *lhs of @p parent.
|
||||||
|
* if assignment creates a cross-generational or cross-checkpoint pointer,
|
||||||
|
* add mutation log entry
|
||||||
|
**/
|
||||||
|
template <typename T>
|
||||||
|
static void assign_member(gp<Object> parent, gp<T> * lhs, gp<Object> rhs);
|
||||||
|
|
||||||
/** use from GC aux functions **/
|
/** use from GC aux functions **/
|
||||||
static gc::GC * _gc() { return reinterpret_cast<gc::GC*>(mm); }
|
static gc::GC * _gc() { return reinterpret_cast<gc::GC*>(mm); }
|
||||||
|
|
||||||
|
|
@ -127,6 +132,13 @@ namespace xo {
|
||||||
* initially all reachable objects are black.
|
* initially all reachable objects are black.
|
||||||
* GC is complete when all reachable objects are white.
|
* GC is complete when all reachable objects are white.
|
||||||
* GC needs a variable amount of temporary storage to keep track of all gray objects
|
* GC needs a variable amount of temporary storage to keep track of all gray objects
|
||||||
|
*
|
||||||
|
* Evacuate reachable object graph rooted at @p src to to-space.
|
||||||
|
* On return all objects reachable from @p src are white
|
||||||
|
*
|
||||||
|
* @param src address of object to evacuate
|
||||||
|
* @param gc garbage collector
|
||||||
|
* @param stats per-object-type GC statistics
|
||||||
**/
|
**/
|
||||||
static Object * _deep_move(Object * src, gc::GC * gc, gc::ObjectStatistics * stats);
|
static Object * _deep_move(Object * src, gc::GC * gc, gc::ObjectStatistics * stats);
|
||||||
|
|
||||||
|
|
@ -213,6 +225,15 @@ namespace xo {
|
||||||
virtual std::size_t _forward_children() = 0;
|
virtual std::size_t _forward_children() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void
|
||||||
|
Object::assign_member(gp<Object> parent, gp<T> * lhs, gp<Object> rhs)
|
||||||
|
{
|
||||||
|
Object::mm->assign_member(parent.ptr(),
|
||||||
|
reinterpret_cast<Object **>(lhs->ptr_address()),
|
||||||
|
rhs.ptr());
|
||||||
|
}
|
||||||
|
|
||||||
/** @class Cpof
|
/** @class Cpof
|
||||||
* @brief argument to operator new used for garbage collector evacuation phase
|
* @brief argument to operator new used for garbage collector evacuation phase
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ namespace xo {
|
||||||
return dest_.ptr() + offset;
|
return dest_.ptr() + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object *
|
||||||
|
Forwarding1::_destination() {
|
||||||
|
return dest_.ptr();
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
Forwarding1::_shallow_size() const {
|
Forwarding1::_shallow_size() const {
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
|
||||||
488
src/alloc/GC.cpp
488
src/alloc/GC.cpp
|
|
@ -71,6 +71,51 @@ namespace xo {
|
||||||
<< ">";
|
<< ">";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MutationLogEntry::is_child_forwarded() const
|
||||||
|
{
|
||||||
|
assert(!parent_->_is_forwarded());
|
||||||
|
|
||||||
|
return (*lhs_)->_is_forwarded();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MutationLogEntry::is_parent_forwarded() const
|
||||||
|
{
|
||||||
|
return parent_->_is_forwarded();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object *
|
||||||
|
MutationLogEntry::parent_destination() const
|
||||||
|
{
|
||||||
|
//const bool c_debug_flag = true;
|
||||||
|
//scope log(XO_DEBUG(c_debug_flag));
|
||||||
|
|
||||||
|
if (parent_->_is_forwarded()) {
|
||||||
|
//log && log("parent is forwarded", xtag("parent", (void*)parent_));
|
||||||
|
|
||||||
|
return parent_->_destination();
|
||||||
|
} else {
|
||||||
|
//log && log("parent is ordinary", xtag("parent", (void*)parent_));
|
||||||
|
|
||||||
|
return parent_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MutationLogEntry
|
||||||
|
MutationLogEntry::update_parent_moved(Object * parent_to) const
|
||||||
|
{
|
||||||
|
std::byte * parent_from = reinterpret_cast<std::byte *>(parent_);
|
||||||
|
std::byte * lhs_from = reinterpret_cast<std::byte *>(lhs_);
|
||||||
|
|
||||||
|
std::ptrdiff_t offset = (lhs_from - parent_from);
|
||||||
|
|
||||||
|
std::byte * lhs_to = reinterpret_cast<std::byte *>(parent_to) + offset;
|
||||||
|
|
||||||
|
return MutationLogEntry(parent_to,
|
||||||
|
reinterpret_cast<Object **>(lhs_to));
|
||||||
|
}
|
||||||
|
|
||||||
GC::GC(const Config & config)
|
GC::GC(const Config & config)
|
||||||
: config_{config}
|
: config_{config}
|
||||||
{
|
{
|
||||||
|
|
@ -89,6 +134,10 @@ namespace xo {
|
||||||
tenured_[role2int(role::to_space) ]
|
tenured_[role2int(role::to_space) ]
|
||||||
= ListAlloc::make("TB", tenured_size, 2 * tenured_size, config.debug_flag_);
|
= ListAlloc::make("TB", tenured_size, 2 * tenured_size, config.debug_flag_);
|
||||||
|
|
||||||
|
mutation_log_[role2int(role::from_space)] = std::make_unique<MutationLog>();
|
||||||
|
mutation_log_[role2int(role::to_space)] = std::make_unique<MutationLog>();
|
||||||
|
defer_mutation_log_ = std::make_unique<MutationLog>();
|
||||||
|
|
||||||
this->checkpoint();
|
this->checkpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +149,13 @@ namespace xo {
|
||||||
return up<GC>{gc};
|
return up<GC>{gc};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string &
|
||||||
|
GC::name() const
|
||||||
|
{
|
||||||
|
static std::string s_default_name = "GC";
|
||||||
|
return s_default_name;
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
GC::size() const
|
GC::size() const
|
||||||
{
|
{
|
||||||
|
|
@ -151,22 +207,34 @@ namespace xo {
|
||||||
return nursery_[role2int(role::to_space)]->after_checkpoint();
|
return nursery_[role2int(role::to_space)]->after_checkpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
generation
|
bool
|
||||||
|
GC::debug_flag() const
|
||||||
|
{
|
||||||
|
return config_.debug_flag_;
|
||||||
|
}
|
||||||
|
|
||||||
|
generation_result
|
||||||
GC::fromspace_generation_of(const void * x) const
|
GC::fromspace_generation_of(const void * x) const
|
||||||
{
|
{
|
||||||
if (tenured_[role2int(role::from_space)]->contains(x))
|
if (tenured_[role2int(role::from_space)]->contains(x))
|
||||||
return generation::tenured;
|
return generation_result::tenured;
|
||||||
|
|
||||||
return generation::nursery;
|
if (nursery_[role2int(role::from_space)]->contains(x))
|
||||||
|
return generation_result::nursery;
|
||||||
|
|
||||||
|
return generation_result::not_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
generation
|
generation_result
|
||||||
GC::generation_of(const void * x) const
|
GC::tospace_generation_of(const void * x) const
|
||||||
{
|
{
|
||||||
if (tenured_[role2int(role::to_space)]->contains(x))
|
if (tenured_[role2int(role::to_space)]->contains(x))
|
||||||
return generation::tenured;
|
return generation_result::tenured;
|
||||||
|
|
||||||
return generation::nursery;
|
if (nursery_[role2int(role::to_space)]->contains(x))
|
||||||
|
return generation_result::nursery;
|
||||||
|
|
||||||
|
return generation_result::not_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::byte *
|
std::byte *
|
||||||
|
|
@ -184,6 +252,11 @@ namespace xo {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t
|
||||||
|
GC::mlog_size() const {
|
||||||
|
return mutation_log_[role2int(role::to_space)]->size();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GC::clear()
|
GC::clear()
|
||||||
{
|
{
|
||||||
|
|
@ -231,39 +304,55 @@ namespace xo {
|
||||||
{
|
{
|
||||||
scope log(XO_DEBUG(config_.debug_flag_), xtag("z", z), xtag("+pad", IAlloc::alloc_padding(z)));
|
scope log(XO_DEBUG(config_.debug_flag_), xtag("z", z), xtag("+pad", IAlloc::alloc_padding(z)));
|
||||||
|
|
||||||
generation g = this->fromspace_generation_of(src);
|
generation_result gr = this->fromspace_generation_of(src);
|
||||||
|
|
||||||
std::byte * retval = nullptr;
|
std::byte * retval = nullptr;
|
||||||
|
|
||||||
if (g == generation::tenured)
|
switch (gr) {
|
||||||
{
|
case generation_result::tenured:
|
||||||
log && log("tenured");
|
{
|
||||||
|
log && log("tenured");
|
||||||
|
|
||||||
retval = tenured_[role2int(role::to_space)]->alloc(z);
|
retval = tenured_[role2int(role::to_space)]->alloc(z);
|
||||||
} else if (nursery_[role2int(role::from_space)]->is_before_checkpoint(src))
|
|
||||||
{
|
|
||||||
log && log("promote");
|
|
||||||
|
|
||||||
/* nursery object has survived 2nd collection cycle
|
|
||||||
* -> promote into tenured generation
|
|
||||||
*/
|
|
||||||
retval = tenured_[role2int(role::to_space)]->alloc(z);
|
|
||||||
|
|
||||||
this->gc_statistics_.total_promoted_ += IAlloc::with_padding(z);
|
|
||||||
} else {
|
|
||||||
log && log("nursery");
|
|
||||||
|
|
||||||
retval = nursery_[role2int(role::to_space)]->alloc(z);
|
|
||||||
|
|
||||||
if (!retval) {
|
|
||||||
/* nursery space exhausted */
|
|
||||||
|
|
||||||
this->request_gc(generation::nursery);
|
|
||||||
|
|
||||||
nursery_[role2int(role::to_space)]->release_redline_memory();
|
|
||||||
|
|
||||||
retval = nursery_[role2int(role::to_space)]->alloc(z);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case generation_result::nursery:
|
||||||
|
{
|
||||||
|
if (nursery_[role2int(role::from_space)]->is_before_checkpoint(src))
|
||||||
|
{
|
||||||
|
/* nursery object has survived 2nd collection cycle
|
||||||
|
* -> promote into tenured generation
|
||||||
|
*/
|
||||||
|
retval = tenured_[role2int(role::to_space)]->alloc(z);
|
||||||
|
|
||||||
|
log && log("promote", xtag("addr", (void*)retval));
|
||||||
|
|
||||||
|
assert(this->tospace_generation_of(retval) == generation_result::tenured);
|
||||||
|
|
||||||
|
this->gc_statistics_.total_promoted_ += IAlloc::with_padding(z);
|
||||||
|
} else {
|
||||||
|
log && log("nursery");
|
||||||
|
|
||||||
|
retval = nursery_[role2int(role::to_space)]->alloc(z);
|
||||||
|
|
||||||
|
if (!retval) {
|
||||||
|
/* nursery space exhausted !? */
|
||||||
|
|
||||||
|
this->request_gc(generation::nursery);
|
||||||
|
|
||||||
|
nursery_[role2int(role::to_space)]->release_redline_memory();
|
||||||
|
|
||||||
|
retval = nursery_[role2int(role::to_space)]->alloc(z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case generation_result::not_found:
|
||||||
|
/* something wrong -- we only copy objects that are known to be in from-space
|
||||||
|
*/
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(retval);
|
assert(retval);
|
||||||
|
|
@ -271,6 +360,63 @@ namespace xo {
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GC::assign_member(Object * parent, Object ** lhs, Object * rhs)
|
||||||
|
{
|
||||||
|
++gc_statistics_.n_mutation_;
|
||||||
|
|
||||||
|
*lhs = rhs;
|
||||||
|
|
||||||
|
if (runstate_.in_progress()) {
|
||||||
|
/* don't log mutations (if any) during GC */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config_.allow_incremental_gc_) {
|
||||||
|
/* full GCs don't need mutation log, since no cross-generational pointers */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tospace_generation_of(rhs))
|
||||||
|
{
|
||||||
|
case generation_result::tenured:
|
||||||
|
/* only need to log mutations that create tenured->nursery pointers */
|
||||||
|
return;
|
||||||
|
|
||||||
|
case generation_result::nursery:
|
||||||
|
switch (tospace_generation_of(parent)) {
|
||||||
|
case generation_result::nursery:
|
||||||
|
if (is_before_checkpoint(parent)) {
|
||||||
|
// N1->N0, so must log
|
||||||
|
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
|
||||||
|
++(this->gc_statistics_.n_logged_mutation_);
|
||||||
|
++(this->gc_statistics_.n_xckp_mutation_);
|
||||||
|
} else {
|
||||||
|
// parent in N0, not an xckp mutation
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case generation_result::tenured:
|
||||||
|
// T->N, so must log
|
||||||
|
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
|
||||||
|
++(this->gc_statistics_.n_logged_mutation_);
|
||||||
|
++(this->gc_statistics_.n_xgen_mutation_);
|
||||||
|
break;
|
||||||
|
case generation_result::not_found:
|
||||||
|
// parent is global
|
||||||
|
// This may be ok (provided lhs is a gc root)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case generation_result::not_found:
|
||||||
|
|
||||||
|
// child is global;
|
||||||
|
// logging not required
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GC::release_redline_memory()
|
GC::release_redline_memory()
|
||||||
{
|
{
|
||||||
|
|
@ -293,10 +439,20 @@ namespace xo {
|
||||||
tenured_[role2int(role::from_space)] = std::move(tmp);
|
tenured_[role2int(role::from_space)] = std::move(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GC::swap_mutation_log()
|
||||||
|
{
|
||||||
|
up<MutationLog> tmp = std::move(mutation_log_[role2int(role::to_space)]);
|
||||||
|
mutation_log_[role2int(role::to_space)] = std::move(mutation_log_[role2int(role::from_space)]);
|
||||||
|
mutation_log_[role2int(role::from_space)] = std::move(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GC::swap_spaces(generation target)
|
GC::swap_spaces(generation target)
|
||||||
{
|
{
|
||||||
// will be copying into storage currently labelled FromSpace
|
scope log(XO_DEBUG(this->debug_flag()));
|
||||||
|
|
||||||
|
// will be copying into the memory regions currently labelled FromSpace
|
||||||
|
|
||||||
/* gc will copy some to-be-determined amount in [0..promote_z]
|
/* gc will copy some to-be-determined amount in [0..promote_z]
|
||||||
from nursery->tenured generation.
|
from nursery->tenured generation.
|
||||||
|
|
@ -321,6 +477,14 @@ namespace xo {
|
||||||
- promote_z
|
- promote_z
|
||||||
+ incr_gc_threshold_);
|
+ incr_gc_threshold_);
|
||||||
this->swap_nursery();
|
this->swap_nursery();
|
||||||
|
|
||||||
|
this->swap_mutation_log();
|
||||||
|
|
||||||
|
log && log(xtag("nursery.from", nursery_[role2int(role::from_space)]->name()));
|
||||||
|
log && log(xtag("nursery.to", nursery_[role2int(role::to_space) ]->name()));
|
||||||
|
log && log(xtag("tenured.from", tenured_[role2int(role::from_space)]->name()));
|
||||||
|
log && log(xtag("tenured.to", tenured_[role2int(role::to_space) ]->name()));
|
||||||
|
|
||||||
} /*swap_spaces*/
|
} /*swap_spaces*/
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -351,6 +515,242 @@ namespace xo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GC::incremental_gc_forward_mlog_phase(MutationLog * from_mlog,
|
||||||
|
MutationLog * to_mlog,
|
||||||
|
MutationLog * defer_mlog,
|
||||||
|
ObjectStatistics * per_type_stats)
|
||||||
|
{
|
||||||
|
scope log(XO_DEBUG(config_.debug_flag_), xtag("from_mlog.size", from_mlog->size()));
|
||||||
|
|
||||||
|
/* categorize pointers based on combination of {source address, destination address},
|
||||||
|
* only care about the generation associated with an address.
|
||||||
|
*
|
||||||
|
* N0 : nursery(from), before checkpoint
|
||||||
|
* N0': nursery(to), before checkpoint
|
||||||
|
* N1 : nursery(from), after checkpoint
|
||||||
|
* N1': nursery(to), after checkpoint
|
||||||
|
* T : tenured(to)
|
||||||
|
*
|
||||||
|
* loc(P): parent region before GC
|
||||||
|
* loc(C): child region before GC
|
||||||
|
*
|
||||||
|
* | | forwarded | loc now post | loc after |
|
||||||
|
* | | already? | root copy | action |
|
||||||
|
* | loc(P) loc(C) | P C | P' C' | P' C' | defer | action
|
||||||
|
* ----|---------------+--------------+---------------+---------------+-------+---------------
|
||||||
|
* (a) | T N0 | no no | T N0 | T N1' | | C->N1', +mlog
|
||||||
|
* (b) | | yes | N1' | N1' | | +mlog
|
||||||
|
* (c) | T N1 | no no | T N1 | T T | | C->T, -mlog
|
||||||
|
* (d) | | yes | T T | T T | | -mlog
|
||||||
|
* (e) | N1 N0 | no no | N1 N0 | N1 N0 | P ->C | defer
|
||||||
|
* (f) | | yes | N1 N1' | N1 N1' | P ->C'| defer
|
||||||
|
* (g) | | yes yes | T N1' | T N1' | | +mlog
|
||||||
|
*
|
||||||
|
* notes:
|
||||||
|
* (a) C survives due to xgen ptr {T -> N0}; after collection have xgen ptr {T -> N1}.
|
||||||
|
* (b) C already evac'd; after collection stil have xgen ptr {T -> N1}
|
||||||
|
* (c) C survives due to xgen ptr (T -> N1): promote to T, so no longer xgen
|
||||||
|
* (d) C already evac'd: after collection no longer xgen (T -> T)
|
||||||
|
* (e) P,C maybe garbage. don't move either, but defer mlog incase P saved by a subsequent mutation.
|
||||||
|
* in that case C saved alto, + will still have an xgen ptr, so still need an mlog entry
|
||||||
|
* (f) P maybe garbage, C survives. defer mlog incase P saved+promoted by a subsequent mutation;
|
||||||
|
* in that case will still have an xgen (T -> N) ptr, so still need an mlog entry.
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::size_t i_from = 0;
|
||||||
|
// number of rescued subgraphs via mutation log entries
|
||||||
|
std::size_t n_rescue = 0;
|
||||||
|
|
||||||
|
for (MutationLogEntry & from_entry : *from_mlog)
|
||||||
|
{
|
||||||
|
if (log) {
|
||||||
|
if (i_from % 10000 == 0)
|
||||||
|
log(xtag("i_from", i_from));
|
||||||
|
}
|
||||||
|
|
||||||
|
void * parent = from_entry.parent();
|
||||||
|
|
||||||
|
if (tospace_generation_of(parent) == generation_result::tenured)
|
||||||
|
{
|
||||||
|
// cases (a)(b)(c)(d)
|
||||||
|
// loc(P) is T. T didn't move b/c incremental gc.
|
||||||
|
|
||||||
|
if (from_entry.is_dead()) {
|
||||||
|
// obsolete mutation -- no longer belongs to parent, discard
|
||||||
|
} else {
|
||||||
|
// note: child obtained (as it must be) by reading from parent's memory _now_.
|
||||||
|
Object * child_from = from_entry.child();
|
||||||
|
|
||||||
|
if (child_from) {
|
||||||
|
if (!child_from->_is_forwarded()) {
|
||||||
|
// P->C*.
|
||||||
|
// either:
|
||||||
|
// - C*=C in from-space, so needs evac
|
||||||
|
// - C*=C' in to-space, P already updated b/c of another mutation
|
||||||
|
//
|
||||||
|
if (fromspace_generation_of(child_from) != generation_result::not_found) {
|
||||||
|
// C*=C in from-space. needs evac, along with reachable descendants
|
||||||
|
//
|
||||||
|
// Includes cases:
|
||||||
|
// (a) T->N0
|
||||||
|
// (c) T->N1
|
||||||
|
|
||||||
|
++n_rescue;
|
||||||
|
|
||||||
|
Object::_deep_move(child_from, this, per_type_stats);
|
||||||
|
|
||||||
|
// C forwards to C', fall thru to parent fixup below
|
||||||
|
// (a) T->N1'
|
||||||
|
// (c) T->T
|
||||||
|
} else {
|
||||||
|
// P updated via some other mutation
|
||||||
|
// so don't need this mlog
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-test, state may have changed above
|
||||||
|
if (from_entry.is_child_forwarded()) {
|
||||||
|
// P->C, C moved to C'
|
||||||
|
// Includes cases (a),(c) from above
|
||||||
|
|
||||||
|
Object * child_to = child_from->_destination();
|
||||||
|
|
||||||
|
from_entry.fixup_parent_child_moved(child_to);
|
||||||
|
|
||||||
|
// P->C', loc(C') in {N1', T'}
|
||||||
|
|
||||||
|
if (tospace_generation_of(child_to) == generation_result::nursery) {
|
||||||
|
// (b) loc(P)=T, loc(C')=N1'; also case (a)
|
||||||
|
|
||||||
|
// still have xgen pointer, so need mlog for it
|
||||||
|
to_mlog->push_back(from_entry);
|
||||||
|
} else {
|
||||||
|
// (d) loc(P)=T, loc(C')=T; also case (c)
|
||||||
|
// no longer xgen, so does not require mlog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// nullptr child, discard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (from_entry.is_parent_forwarded()) {
|
||||||
|
// Must have:
|
||||||
|
// loc(P) = N1, because:
|
||||||
|
// loc(P)=N0 -> ineligible for mlog;
|
||||||
|
// loc(P)=T -> not moved on incr GC
|
||||||
|
//
|
||||||
|
// follows that loc(P') = T
|
||||||
|
// already have P'->C' when parent moved separately
|
||||||
|
|
||||||
|
Object * parent_to = from_entry.parent_destination();
|
||||||
|
|
||||||
|
log(xtag("parent_to", (void*)parent_to));
|
||||||
|
|
||||||
|
assert(tospace_generation_of(parent_to) == generation_result::tenured);
|
||||||
|
|
||||||
|
MutationLogEntry to_entry = from_entry.update_parent_moved(parent_to);
|
||||||
|
|
||||||
|
Object * child_to = to_entry.child(); // after moving
|
||||||
|
|
||||||
|
if (tospace_generation_of(child_to) == generation_result::nursery) {
|
||||||
|
if (to_entry.is_dead()) {
|
||||||
|
;
|
||||||
|
} else {
|
||||||
|
// (g) loc(P)=N1, loc(C)=N0, loc(P')=T, loc(C')=N1
|
||||||
|
to_mlog->push_back(to_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// loc(P) = N1, loc(C) = N0, P may be garbage
|
||||||
|
// Includes cases:
|
||||||
|
// (e) P->C, C not moved
|
||||||
|
// (f) P->C, C moved to C'
|
||||||
|
//
|
||||||
|
// P may yet be rescued by another mlog entry, so defer
|
||||||
|
|
||||||
|
if (!from_entry.is_dead()) {
|
||||||
|
defer_mlog->push_back(from_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++i_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
from_mlog->clear();
|
||||||
|
|
||||||
|
if (n_rescue == 0) {
|
||||||
|
// if we didn't rescue any objects
|
||||||
|
// then we now confirm that otherwise-unreachable parents in defer_mlog
|
||||||
|
// are garbage
|
||||||
|
|
||||||
|
defer_mlog->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GC::incremental_gc_forward_mlog(ObjectStatistics * per_type_stats)
|
||||||
|
{
|
||||||
|
/* control here:
|
||||||
|
* - incremental gc.
|
||||||
|
* - gc roots have been copied, along with everything reachable from them.
|
||||||
|
*
|
||||||
|
* plan:
|
||||||
|
* - forward mutation in *from_mutation_log, writing them to
|
||||||
|
* *to_mutationlog and/or *defer_mutation_log.
|
||||||
|
* Use defer when mutation P->C encountered, but P was not copied.
|
||||||
|
* P appears to be garbage, but may turn out to be live if encountered
|
||||||
|
* in another mutation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
MutationLog * to_mlog = mutation_log_[role2int(role::to_space)].get();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
MutationLog * from_mlog = mutation_log_[role2int(role::from_space)].get();
|
||||||
|
MutationLog * defer_mlog = defer_mutation_log_.get();
|
||||||
|
|
||||||
|
this->incremental_gc_forward_mlog_phase(from_mlog,
|
||||||
|
to_mlog,
|
||||||
|
defer_mlog,
|
||||||
|
per_type_stats);
|
||||||
|
|
||||||
|
assert(from_mlog->empty());
|
||||||
|
|
||||||
|
if (defer_mlog->empty()) {
|
||||||
|
/* fixpoint reached */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control here:
|
||||||
|
* 1. at least one mlog triggered a rescue
|
||||||
|
* 2. at least one mlog was deferred (b/c otherwise-unreachable parent)
|
||||||
|
*
|
||||||
|
* it's conceivable deferred parent now reachable thanks to rescues;
|
||||||
|
* revisit entries in defer_mlog,
|
||||||
|
*
|
||||||
|
* using now-empty from_mlog as scratch for any remaining deferred entries
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::swap(mutation_log_[role2int(role::from_space)], defer_mutation_log_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GC::forward_mutation_log(generation upto)
|
||||||
|
{
|
||||||
|
scope log(XO_DEBUG(config_.debug_flag_));
|
||||||
|
|
||||||
|
if (upto == generation::tenured) {
|
||||||
|
log && log("TODO: forward mutation log for full GC");
|
||||||
|
} else {
|
||||||
|
this->incremental_gc_forward_mlog(&gc_statistics_.per_type_stats_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GC::cleanup_phase(generation upto)
|
GC::cleanup_phase(generation upto)
|
||||||
{
|
{
|
||||||
|
|
@ -401,11 +801,11 @@ namespace xo {
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GC::execute_gc(generation target)
|
GC::execute_gc(generation upto)
|
||||||
{
|
{
|
||||||
scope log(XO_DEBUG(config_.debug_flag_));
|
scope log(XO_DEBUG(config_.debug_flag_));
|
||||||
|
|
||||||
bool full_move = (target == generation::tenured);
|
bool full_move = (upto == generation::tenured);
|
||||||
|
|
||||||
// TODO: RAII version in case of exceptions
|
// TODO: RAII version in case of exceptions
|
||||||
this->runstate_ = GCRunstate(true /*in_progress*/, full_move);
|
this->runstate_ = GCRunstate(true /*in_progress*/, full_move);
|
||||||
|
|
@ -415,7 +815,7 @@ namespace xo {
|
||||||
/* new allocation since last GC */
|
/* new allocation since last GC */
|
||||||
std::size_t new_alloc = this->after_checkpoint();
|
std::size_t new_alloc = this->after_checkpoint();
|
||||||
|
|
||||||
++(gc_statistics_.gen_v_[static_cast<std::size_t>(target)].n_gc_);
|
++(gc_statistics_.gen_v_[static_cast<std::size_t>(upto)].n_gc_);
|
||||||
gc_statistics_.total_allocated_ += new_alloc;
|
gc_statistics_.total_allocated_ += new_alloc;
|
||||||
gc_statistics_.total_promoted_sab_ = gc_statistics_.total_promoted_;
|
gc_statistics_.total_promoted_sab_ = gc_statistics_.total_promoted_;
|
||||||
|
|
||||||
|
|
@ -423,15 +823,17 @@ namespace xo {
|
||||||
|
|
||||||
log && log("step 1: swap to/from roles");
|
log && log("step 1: swap to/from roles");
|
||||||
|
|
||||||
this->swap_spaces(target);
|
this->swap_spaces(upto);
|
||||||
|
|
||||||
log && log("step 2a: copy globals");
|
log && log("step 2a: copy globals");
|
||||||
|
|
||||||
this->copy_globals(target);
|
this->copy_globals(upto);
|
||||||
|
|
||||||
log && log("step 2b: TODO: copy pinned");
|
log && log("step 2b: TODO: copy pinned");
|
||||||
|
|
||||||
log && log("step 3: TODO: forward mutation log");
|
log && log("step 3: forward mutation log");
|
||||||
|
|
||||||
|
this->forward_mutation_log(upto);
|
||||||
|
|
||||||
log && log("step 4: TODO: notify destructor log");
|
log && log("step 4: TODO: notify destructor log");
|
||||||
|
|
||||||
|
|
@ -439,7 +841,7 @@ namespace xo {
|
||||||
|
|
||||||
log && log("step 6: cleanup");
|
log && log("step 6: cleanup");
|
||||||
|
|
||||||
this->cleanup_phase(target);
|
this->cleanup_phase(upto);
|
||||||
|
|
||||||
this->runstate_ = GCRunstate();
|
this->runstate_ = GCRunstate();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,12 @@ namespace xo {
|
||||||
return z + alloc_padding(z);
|
return z + alloc_padding(z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
IAlloc::assign_member(Object * /*parent*/, Object ** lhs, Object * rhs)
|
||||||
|
{
|
||||||
|
*lhs = rhs;
|
||||||
|
}
|
||||||
|
|
||||||
std::byte *
|
std::byte *
|
||||||
IAlloc::alloc_gc_copy(std::size_t /*z*/, const void * /*src*/)
|
IAlloc::alloc_gc_copy(std::size_t /*z*/, const void * /*src*/)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,16 @@ namespace xo {
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string &
|
||||||
|
ListAlloc::name() const {
|
||||||
|
if (hd_) {
|
||||||
|
return hd_->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string s_default_name = "ListAlloc";
|
||||||
|
return s_default_name;
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
ListAlloc::size() const {
|
ListAlloc::size() const {
|
||||||
return total_z_;
|
return total_z_;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace xo {
|
||||||
|
|
||||||
bool full_move = gc->runstate().full_move();
|
bool full_move = gc->runstate().full_move();
|
||||||
|
|
||||||
if (!full_move && (gc->generation_of(src) == gc::generation::tenured)) {
|
if (!full_move && (gc->tospace_generation_of(src) == gc::generation_result::tenured)) {
|
||||||
/* don't move tenured objects during incremental collection */
|
/* don't move tenured objects during incremental collection */
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ namespace xo {
|
||||||
|
|
||||||
bool full_move = gc->runstate().full_move();
|
bool full_move = gc->runstate().full_move();
|
||||||
|
|
||||||
if (!full_move && gc->generation_of(from_src) == generation::tenured) {
|
if (!full_move && gc->tospace_generation_of(from_src) == gc::generation_result::tenured) {
|
||||||
/** incremental collection does not move already-tenured objects **/
|
/** incremental collection does not move already-tenured objects **/
|
||||||
return from_src;
|
return from_src;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
# build unittest alloc/utest
|
# build unittest alloc/utest
|
||||||
|
#
|
||||||
|
# NOTE: more GC tests in xo-object/utest
|
||||||
|
|
||||||
set(SELF_EXE utest.alloc)
|
set(UTEST_EXE utest.alloc)
|
||||||
set(SELF_SRCS
|
set(UTEST_SRCS
|
||||||
alloc_utest_main.cpp
|
alloc_utest_main.cpp
|
||||||
ArenaAlloc.test.cpp
|
ArenaAlloc.test.cpp
|
||||||
GC.test.cpp)
|
GC.test.cpp)
|
||||||
|
|
||||||
xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS})
|
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
|
||||||
xo_self_dependency(${SELF_EXE} xo_alloc)
|
xo_self_dependency(${UTEST_EXE} xo_alloc)
|
||||||
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
|
xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2)
|
||||||
|
|
|
||||||
|
|
@ -65,5 +65,8 @@ namespace xo {
|
||||||
REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 1);
|
REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /*namespace ut*/
|
} /*namespace ut*/
|
||||||
} /*namespace xo*/
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* GC.test.cpp */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue