Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions doc/developer-guide/internal-libraries/Extendible.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,19 @@ the type's constructor, destructor, and serializer. And to avoid corruption, the
}


When an derived class is instantiated, :func:`template<> alloc()` will allocate a block of memory for the derived class and all added
fields. The only memory overhead per instance is an uint16 used as a offset to the start of the extendible block.
When an derived class is instantiated, :func:`template<> create()` will allocate a block of memory for the derived class and all added
fields. The only memory overhead per instance is an uint16 used as a offset to the start of the extendible block. Then the constructor of the class
is called, followed by the constructors of each extendible field.

.. code-block:: cpp

ExtendibleExample* alloc_example() {
return ext::alloc<ExtendibleExample>();
return ext::create<ExtendibleExample>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be "make_extendible" to be stylistically consistent with STL (e.g. "make_shared", "make_unique").

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that thought, but make_shared takes a pre-allocated class and returns an enhanced pointer to it, so it's not a perfect match. It would be interesting to make a type of extendible to work that way, but you lose the benefits of continuous memory. allocate_shared<T>() is closer to what I'm doing here. But I renamed 'from alloc to create to callout that it doesn't just return a memory chunk, it has constructed and initialized all fields and internal offsets.

}

Memory Layout
-------------
One block of memory is allocated per |Extendible|, which included all member variables and extended fields.
One block of memory is allocated per |Extendible|, which include all member variables and extended fields.
Within the block, memory is arranged in the following order:

#. Derived members (+padding align next field)
Expand Down Expand Up @@ -129,8 +130,8 @@ which simplifies the code using it. Also this provides compile errors for common
}

PluginFunc() {
Food *banana = ext::alloc<Food>();
Car *camry = ext::alloc<Car>();
Food *banana = ext::create<Food>();
Car *camry = ext::create<Car>();

// Common user errors

Expand All @@ -140,11 +141,11 @@ which simplifies the code using it. Also this provides compile errors for common

float speed = ext::get(banana,fld_max_speed);
// ^^^^^^^^^^^^^
// Compile error: Cannot downcast banana to type Extendible<Car>
// Compile error: Cannot convert banana to type Extendible<Car>

float weight = ext::get(camry,fld_food_weight);
// ^^^^^^^^^^^^^^^
// Compile error: Cannot downcast camry to type Extendible<Food>, even though Car and Food each have a 'weight' field, the FieldId is strongly typed.
// Compile error: Cannot convert camry to type Extendible<Food>, even though Car and Food each have a 'weight' field, the FieldId is strongly typed.

}

Expand All @@ -157,20 +158,20 @@ which simplifies the code using it. Also this provides compile errors for common

Fruit.schema.addField(fld_has_seeds, "has_seeds");

Fruit mango = ext::alloc<Fruit>();
Fruit mango = ext::create<Fruit>();

ext::set(mango, fld_has_seeds) = true; // downcasts mango to Extendible<Fruit>
ext::set(mango, fld_food_weight) = 2; // downcasts mango to Extendible<Food>
ext::set(mango, fld_has_seeds) = true; // converts mango to Extendible<Fruit>
ext::set(mango, fld_food_weight) = 2; // converts mango to Extendible<Food>
ext::set(mango, fld_max_speed) = 9;
// ^^^^^^^^^^^^^
// Compile error: Cannot downcast mango to type Extendible<Car>
// Compile error: Cannot convert mango to type Extendible<Car>


Inheritance
-----------

Unfortunately it is non-trivial handle multiple |Extendible| super types in the same inheritance tree.
:func:`template<> alloc()` handles allocation and initialization of the entire `Derived` class, but it is dependent on each class defining :code:`using super_type = *some_super_class*;` so that it recurse through the classes.
:func:`template<> create()` handles allocation and initialization of the entire `Derived` class, but it is dependent on each class defining :code:`using super_type = *some_super_class*;` so that it recurse through the classes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"it can correctly track the class hierarchy"


.. code-block:: cpp

Expand All @@ -191,7 +192,7 @@ Inheritance
ext::FieldId<A, atomic<uint16_t>> ext_a_1;
ext::FieldId<C, uint16_t> ext_c_1;

C &x = *(ext::alloc<C>());
C &x = *(ext::create<C>());
ext::viewFormat(x);

:func:`viewFormat` prints a diagram of the position and size of bytes used within the allocated
Expand Down Expand Up @@ -235,8 +236,9 @@ Namespace `ext`

one schema instance per |Extendible| to define contained |FieldDesc|

.. function:: template<typename Derived_t> Extendible* alloc()
.. function:: template<typename Derived_t> Extendible* create()

To be used in place of `new Derived_t()`.
Allocate a block of memory. Construct the base data.
Recursively construct and initialize `Derived_t::super_type` and its |Extendible| classes.

Expand Down
58 changes: 41 additions & 17 deletions include/tscore/Extendible.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
#include "tscore/ink_memory.h"
#include "tscore/ink_defs.h"

//////////////////////////////////////////
/// SUPPORT MACRO
#define DEF_EXT_NEW_DEL(cls) \
void *operator new(size_t sz) { return ats_malloc(ext::sizeOf<cls>()); } \
void *operator new(size_t sz, void *ptr) { return ptr; } \
void operator delete(void *ptr) { free(ptr); }

//////////////////////////////////////////
/// HELPER CLASSES

Expand Down Expand Up @@ -141,9 +148,11 @@ namespace details // internal stuff
{
public:
std::unordered_map<std::string, FieldDesc> fields; ///< defined elements of the blob by name
size_t alloc_size = 0; ///< bytes to allocate for fields
uint8_t alloc_align = 1; ///< alignment of block
std::atomic_uint instance_count = {0}; ///< the number of Extendible<Derived> instances in use.
size_t alloc_size = 0; ///< bytes to allocate for fields
uint8_t alloc_align = 1; ///< alignment of block
std::atomic<uint> cnt_constructed = {0}; ///< the number of Extendible<Derived> created.
std::atomic<uint> cnt_fld_constructed = {0}; ///< the number of Extendible<Derived> that constructed fields.
std::atomic<uint> cnt_destructed = {0}; ///< the number of Extendible<Derived> destroyed.

public:
Schema() {}
Expand All @@ -163,7 +172,7 @@ namespace details // internal stuff

/// ext::Extendible allows code (and Plugins) to declare member-like variables during system init.
/*
* This class uses a special allocator (ext::alloc) to extend the memory allocated to store run-time static
* This class uses a special allocator (ext::create) to extend the memory allocated to store run-time static
* variables, which are registered by plugins during system init. The API is in a functional style to support
* multiple inheritance of Extendible classes. This is templated so static variables are instanced per Derived
* type, because we need to have different field schema per type.
Expand Down Expand Up @@ -199,10 +208,10 @@ template <typename Derived_t> class Extendible

protected:
Extendible();
// use ext::alloc() exclusively for allocation and initialization
// use ext::create() exclusively for allocation and initialization

/** destruct all fields */
~Extendible() { schema.callDestructor(uintptr_t(this) + ext_loc); }
~Extendible();

private:
/** construct all fields */
Expand All @@ -214,7 +223,7 @@ template <typename Derived_t> class Extendible
return uintptr_t(this) + ext_loc;
}

template <typename T> friend T *alloc();
template <typename T> friend T *create();
template <typename T> friend uintptr_t details::initRecurseSuper(T &, uintptr_t);
template <typename T> friend FieldPtr details::FieldPtrGet(Extendible<T> const &, details::FieldDesc const &);
template <typename T> friend std::string viewFormat(T const &, uintptr_t, int);
Expand Down Expand Up @@ -575,6 +584,17 @@ sizeOf(size_t size = sizeof(Derived_t))
template <class Derived_t> Extendible<Derived_t>::Extendible()
{
ink_assert(ext::details::areFieldsFinalized());
// don't call callConstructor until the derived class is fully constructed.
++schema.cnt_constructed;
}

template <class Derived_t> Extendible<Derived_t>::~Extendible()
{
// assert callConstructors was called.
ink_assert(ext_loc);
schema.callDestructor(uintptr_t(this) + ext_loc);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use std::destroy_at here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think not. schema like the type eraser field factory. callDestructor executes functors/lambdas for each field on that extendible block of memory.

++schema.cnt_destructed;
ink_assert(schema.cnt_destructed <= schema.cnt_fld_constructed);
}

/// tell this extendible where it's memory offset start is. Added to support inheriting from extendible classes
Expand All @@ -584,8 +604,9 @@ Extendible<Derived_t>::initFields(uintptr_t start_ptr)
{
ink_assert(ext_loc == 0);
start_ptr = ROUNDUP(start_ptr, schema.alloc_align); // pad the previous struct, so that our fields are memaligned correctly
ext_loc = uint16_t(start_ptr - uintptr_t(this)); // store the offset to be used by ext::get and ext::set
ink_assert(ext_loc < 256);
ink_assert(start_ptr - uintptr_t(this) < UINT16_MAX);
ext_loc = uint16_t(start_ptr - uintptr_t(this)); // store the offset to be used by ext::get and ext::set
ink_assert(ext_loc > 0);
schema.callConstructor(start_ptr); // construct all fields
return start_ptr + schema.alloc_size; // return the end of the extendible data
}
Expand All @@ -608,23 +629,26 @@ namespace details
}
return tail_ptr;
}

} // namespace details

// allocate and initialize an extendible data structure
template <typename Derived_t>
template <typename Derived_t, typename... Args>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need typname && ... Args for perfect forwarding.

Derived_t *
alloc()
create(Args &&... args)
{
static_assert(std::is_base_of<Extendible<Derived_t>, Derived_t>::value);
// don't instantiate until all Fields are finalized.
ink_assert(ext::details::areFieldsFinalized());

// calculate the memory needed
// calculate the memory needed for the class and all Extendible blocks
const size_t type_size = ext::sizeOf<Derived_t>();
// alloc a block of memory
Derived_t *ptr = (Derived_t *)ats_memalign(alignof(Derived_t), type_size);
// Extendible_t *ptr = (Extendible_t *)::operator new(type_size); // alloc a block of memory

// alloc one block of memory
Derived_t *ptr = static_cast<Derived_t *>(ats_memalign(alignof(Derived_t), type_size));

// construct (recursively super-to-sub class)
new (ptr) Derived_t();
new (ptr) Derived_t(std::forward(args)...);

// define extendible blocks start offsets (recursively super-to-sub class)
details::initRecurseSuper(*ptr, uintptr_t(ptr) + sizeof(Derived_t));
return ptr;
Expand Down
11 changes: 6 additions & 5 deletions src/tscore/Extendible.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ namespace details
void
Schema::updateMemOffsets()
{
ink_release_assert(instance_count == 0);
ink_release_assert(cnt_constructed == cnt_destructed);

uint32_t acc_offset = 0;
alloc_align = 1;
Expand Down Expand Up @@ -86,7 +86,7 @@ namespace details
bool
Schema::reset()
{
if (instance_count > 0) {
if (cnt_constructed > cnt_destructed) {
// free instances before calling this so we don't leak memory
return false;
}
Expand All @@ -99,7 +99,9 @@ namespace details
Schema::callConstructor(uintptr_t ext_loc)
{
ink_assert(ext_loc);
++instance_count; // don't allow schema modification
++cnt_fld_constructed; // don't allow schema modification
ink_assert(cnt_fld_constructed <= cnt_constructed);

// init all extendible memory to 0, incase constructors don't
memset(reinterpret_cast<void *>(ext_loc), 0, alloc_size);

Expand All @@ -119,7 +121,6 @@ namespace details
elm.second.destructor(FieldPtr(ext_loc + elm.second.field_offset));
}
}
--instance_count;
}

size_t
Expand All @@ -132,7 +133,7 @@ namespace details
bool
Schema::no_instances() const
{
return instance_count == 0;
return cnt_constructed == cnt_destructed;
}
} // namespace details

Expand Down
Loading