-
Notifications
You must be signed in to change notification settings - Fork 857
Extendible asan simple #6650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extendible asan simple #6650
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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>(); | ||
| } | ||
|
|
||
| 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) | ||
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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. | ||
|
|
||
| } | ||
|
|
||
|
|
@@ -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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "it can correctly track the class hierarchy" |
||
|
|
||
| .. code-block:: cpp | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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() {} | ||
|
|
@@ -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. | ||
|
|
@@ -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 */ | ||
|
|
@@ -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); | ||
|
|
@@ -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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you use
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -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 | ||
| } | ||
|
|
@@ -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> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need |
||
| 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; | ||
|
|
||
There was a problem hiding this comment.
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").
There was a problem hiding this comment.
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 'fromalloctocreateto callout that it doesn't just return a memory chunk, it has constructed and initialized all fields and internal offsets.