trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a..@apache.org
Subject [trafficserver] branch master updated: Inherited Extendible
Date Fri, 17 May 2019 20:19:33 GMT
This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new dee68b4  Inherited Extendible
dee68b4 is described below

commit dee68b4dffb50a2de776723437323aa872a98363
Author: Aaron Canary <acanary@oath.com>
AuthorDate: Thu Jan 3 15:05:42 2019 -0600

    Inherited Extendible
    
    Refactored Extendible class to be used with multiple inheritance, for NetVConn.
    
    * Refactored all access to functional style; ext::get, ext::set.
    * Added alloc, sizeOf to manage lifetime of extendible classes.
    * Added toString to inspect all values of extendibles.
    * Added viewFormat to render ascii chart of memory alignments of class and extendible memory.
    
    compile fix
    
    
    compile fix2
    
    
    fix
    
    
    fix
    
    
    typecheck
    
    
    formatting test for different size of strings.
    
    
    replaced toString CHECKs
    
    
    string contains
    
    
    include array
---
 .../internal-libraries/AcidPtr.en.rst              |    2 +-
 .../internal-libraries/Extendible.en.rst           |  297 +++---
 include/tscore/Extendible.h                        | 1027 ++++++++++++--------
 src/tscore/Extendible.cc                           |  139 +++
 src/tscore/Makefile.am                             |    3 +
 src/tscore/unit_tests/test_AcidPtr.cc              |    2 +-
 src/tscore/unit_tests/test_Extendible.cc           |  510 ++++++----
 7 files changed, 1251 insertions(+), 729 deletions(-)

diff --git a/doc/developer-guide/internal-libraries/AcidPtr.en.rst b/doc/developer-guide/internal-libraries/AcidPtr.en.rst
index 22d6e04..4bbf761 100644
--- a/doc/developer-guide/internal-libraries/AcidPtr.en.rst
+++ b/doc/developer-guide/internal-libraries/AcidPtr.en.rst
@@ -34,7 +34,7 @@ Synopsis
 
 .. code-block:: cpp
 
-   #include "ts/AcidPtr.h"
+   #include "tscore/AcidPtr.h"
 
 |AcidPtr| provides atomic access to a std::shared_ptr.
 |AcidCommitPtr| provides exclusive write access to data.
diff --git a/doc/developer-guide/internal-libraries/Extendible.en.rst b/doc/developer-guide/internal-libraries/Extendible.en.rst
index 7fe049a..f386fe0 100644
--- a/doc/developer-guide/internal-libraries/Extendible.en.rst
+++ b/doc/developer-guide/internal-libraries/Extendible.en.rst
@@ -21,17 +21,11 @@
 .. default-domain:: cpp
 
 .. |Extendible| replace:: :class:`Extendible`
-.. |FieldId| replace:: :class:`~Extendible::FieldId`
-.. |FieldId_C| replace:: :type:`FieldId_C`
-.. |FieldSchema| replace:: :class:`~Extendible::FieldSchema`
-.. |Schema| replace:: :class:`~Extendible::Schema`
-
-.. |ATOMIC| replace:: :enumerator:`~AccessEnum::ATOMIC`
-.. |BIT| replace:: :enumerator:`~AccessEnum::BIT`
-.. |ACIDPTR| replace:: :enumerator:`~AccessEnum::ACIDPTR`
-.. |STATIC| replace:: :enumerator:`~AccessEnum::STATIC`
-.. |DIRECT| replace:: :enumerator:`~AccessEnum::DIRECT`
-.. |C_API| replace:: :enumerator:`~AccessEnum::C_API`
+.. |FieldId| replace:: :class:`template<typename Derived_t, typename Field_t> FieldId`
+.. |ExtFieldContext| replace:: :type:`ExtFieldContext`
+.. |FieldDesc| replace:: :class:`FieldDesc`
+.. |Schema| replace:: :class:`Schema`
+
 
 .. _Extendible:
 
@@ -43,218 +37,291 @@ Synopsis
 
 .. code-block:: cpp
 
-   #include "ts/Extendible.h"
+   #include "tscore/Extendible.h"
 
-|Extendible| allows Plugins to append additional storage to Core data structures.
+|Extendible| allows Plugins to append additional storage to Core data structures and interface like a map or dictionary. Each additional field is declared during init, so that a custom allocator can malloc one block for the Datatype and its extended fields.
+In C++, the |FieldId| are strongly typed field handles, which allows you to use Extendible in multiple inheritance, and at many levels of inheritance hierarchy, with compile time type safety.
 
 Use Case:
 
-TSCore
+  TSCore
+
+    Defines class ``Host`` as |Extendible|
 
-  Defines class ``Host`` as |Extendible|
+  TSPlugin ``HealthStatus``
 
-TSPlugin ``HealthStatus``
+    Extend the ``Host`` datatype with field ``<int> down reason code``. API returns a handle.
 
-  Appends field ``"down reason code"`` to ``Host``
-  Now the plugin does not need to create completely new lookup table and all implementation that comes with it.
+    Use the (Data*, handle) to read & write fields.
 
 
 Description
 +++++++++++
 
-A data class that inherits from Extendible, uses a CRTP (Curiously Recurring Template Pattern) so that its static |Schema| instance is unique among other |Extendible| types.
+A data class that inherits from Extendible, uses a CRTP (Curiously Recurring Template Pattern)
+so that its static |Schema| instance is unique among other |Extendible| types. Thus all instances of the type implicitly know memory layout of the fields.
 
 .. code-block:: cpp
 
-   class ExtendibleExample : Extendible<ExtendibleExample> {
+   class ExtendibleExample : public ext::Extendible<ExtendibleExample> {
       int real_member = 0;
    }
 
+The documentation and code refers to the `Derived` type as the class that is inheriting from an |Extendible|.
 
-During system init, code and plugins can add fields to the |Extendible|'s schema. This will update the `Memory Layout`_ of the schema, and the memory offsets of all fields. The schema does not know the field's type, but it stores the byte size and creates lambdas of the type's constructor and destructor. And to avoid corruption, the code asserts that no instances are in use when adding fields.
+During system init, code and plugins add fields to the |Extendible|'s schema. This will update the `Memory Layout`_ of the schema,
+and the memory offsets of all fields. The schema does not know the field's type, but it stores the byte size and creates std::functions of
+the type's constructor, destructor, and serializer. And to avoid corruption, the code asserts that no instances are in use when adding fields.
 
 .. code-block:: cpp
 
-   ExtendibleExample::FieldId<ATOMIC,int> fld_my_int;
+   ext::FieldId<ExtendibleExample,int> fld_my_int;
 
    void PluginInit() {
-     fld_my_int = ExtendibleExample::schema.addField("my_plugin_int");
+     fld_my_int = ext::fieldAdd(fld_my_int, "my_plugin_int");
    }
 
 
-When an |Extendible| derived class is instantiated, :code:`new()` will allocate a block of memory for the derived class and all added fields. There is zero memory overhead per instance, unless using :enumerator:`~AccessEnum::ACIDPTR` field access type.
+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.
 
 .. code-block:: cpp
 
    ExtendibleExample* alloc_example() {
-     return new ExtendibleExample();
+     return ext::alloc<ExtendibleExample>();
    }
 
 Memory Layout
 -------------
-One block of memory is allocated per |Extendible|, which include all member variables and appended fields.
+One block of memory is allocated per |Extendible|, which included all member variables and extended fields.
 Within the block, memory is arranged in the following order:
 
-#. Derived members (+padding align next field)
-#. Fields (largest to smallest)
-#. Packed Bits
+   #. Derived members (+padding align next field)
+   #. Fields (largest to smallest)
+   #. Packed Bits
+
+When using inheritance, all base cases arranged from most super to most derived,
+then all |Extendible| blocks are arranged from most super to most derived.
+If the fields are aligned, padding will be inserted where needed.
 
 Strongly Typed Fields
 ---------------------
-:class:`template<AccessEnum FieldAccess_e, typename T> Extendible::FieldId`
+:class:`template<typename Derived_t, typename Field_t> FieldId`
 
-|FieldId| is a templated ``T`` reference. One benefit is that all type casting is internal to the |Extendible|,
+|FieldId| is a templated ``Field_t`` reference. One benefit is that all type casting is internal to the |Extendible|,
 which simplifies the code using it. Also this provides compile errors for common misuses and type mismatches.
 
 .. code-block:: cpp
 
    // Core code
-   class Food : Extendible<Food> {}
-   class Car : Extendible<Car> {}
+   class Food : public ext::Extendible<Food> {};
+   class Car : public ext::Extendible<Car> {};
 
    // Example Plugin
-   Food::FieldId<STATIC,float> fld_food_weight;
-   Food::FieldId<STATIC,time_t> fld_expr_date;
-   Car::FieldId<STATIC,float> fld_max_speed;
-   Car::FieldId<STATIC,float> fld_car_weight;
+   ext::FieldId<Food,float> fld_food_weight;
+   ext::FieldId<Food,time_t> fld_expr_date;
+   ext::FieldId<Car,float> fld_max_speed;
+   ext::FieldId<Car,float> fld_car_weight;
 
    PluginInit() {
-      Food.schema.addField(fld_food_weight, "weight");
-      Food.schema.addField(fld_expr_date,"expire date");
-      Car.schema.addField(fld_max_speed,"max_speed");
-      Car.schema.addField(fld_car_weight,"weight"); // 'weight' is unique within 'Car'
+      ext::addField(fld_food_weight, "weight");
+      ext::addField(fld_expr_date,"expire date");
+      ext::addField(fld_max_speed,"max_speed");
+      ext::addField(fld_car_weight,"weight"); // 'weight' is unique within 'Car'
    }
 
    PluginFunc() {
-      Food banana;
-      Car camry;
+      Food *banana = ext::alloc<Food>();
+      Car *camry = ext::alloc<Car>();
 
       // Common user errors
 
-      float expire_date = banana.get(fld_expr_date);
-      //^^^                                              Compile error: cannot convert time_t to float
-      float speed = banana.get(fld_max_speed);
-      //                       ^^^^^^^^^^^^^             Compile error: fld_max_speed is not of type Extendible<Food>::FieldId
-      float weight = camry.get(fld_food_weight);
-      //                       ^^^^^^^^^^^^^^^           Compile error: fld_food_weight is not of type Extendible<Car>::FieldId
+      float expire_date = ext::get(banana,fld_expr_date);
+      //^^^
+      // Compile error: cannot convert time_t to float
+
+      float speed = ext::get(banana,fld_max_speed);
+      //                            ^^^^^^^^^^^^^
+      // Compile error: Cannot downcast 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.
+
    }
 
-Field Access Types
-------------------
+   // Inheritance Example
+   class Fruit : Food, Extendible<Fruit> {
+      using super_type = Food;
+   };
 
-.. _AccessType:
+   ext::FieldId<Fruit,has_seeds> fld_has_seeds;
 
-..
-  Currently Sphinx will link to the first overloaded version of the method /
-  function. (As of Sphinx 1.7.6)
+   Fruit.schema.addField(fld_has_seeds, "has_seeds");
 
-.. |GET| replace:: :func:`~Extendible::get`
-.. |READBIT| replace:: :func:`~Extendible::readBit`
-.. |WRITEBIT| replace:: :func:`~Extendible::writeBit`
-.. |WRITEACIDPTR| replace:: :func:`~Extendible::writeAcidPtr`
-.. |INIT| replace:: :func:`~Extendible::init`
+   Fruit mango = ext::alloc<Fruit>();
 
-================   =================================   ================================================================   =================================================   ================================================
-Enums              Allocates                                           API                                                            Pros                                               Cons
-================   =================================   ================================================================   =================================================   ================================================
-|ATOMIC|           ``std::atomic<T>``                  |GET|                                                              Leverages ``std::atomic`` API. No Locking.          Only works on small data types.
-|BIT|              1 bit from packed bits              |GET|, |READBIT|, |WRITEBIT|                                       Memory efficient.                                   Cannot return reference.
-|ACIDPTR|          ``std::make_shared<T>``             |GET|, |WRITEACIDPTR|                                              Avoid skew in non-atomic structures.                Non-contiguous memory allocations. Uses locking.
-|STATIC|           ``T``                               |GET|, |INIT|                                                      Const reference. Fast. Type safe.                   No concurrency protection.
-|DIRECT|           ``T``                               |GET|                                                              Direct reference. Fast. Type safe.                  No concurrency protection.
-|C_API|            a number of bytes                   |GET|                                                              Can use in C.                                       No concurrency protection. Not type safe.
-================   =================================   ================================================================   =================================================   ================================================
+   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_max_speed) = 9;
+   //              ^^^^^^^^^^^^^
+   // Compile error: Cannot downcast mango to type Extendible<Car>
 
-:code:`operator[](FieldId)` has been overridden to call :code:`get(FieldId)` for all access types.
 
-Unfortunately our data is not "one type fits all". I expect that most config values will be stored as :enumerator:`AccessEnum::STATIC`, most states values will be :enumerator:`AccessEnum::ATOMIC` or :enumerator:`AccessEnum::BIT`, while vectored results will be :enumerator:`AccessEnum::ACIDPTR`.
+Inheritance
+-----------
 
-Reference
-+++++++++
+   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.
+
+.. code-block:: cpp
+
+   struct A : public Extendible<A> {
+      uint16_t a = {1};
+   };
+
+   struct B : public A {
+      using super_type = A;
+      uint16_t b       = {2};
+   };
 
-.. enum:: AccessEnum
+   struct C : public B, public Extendible<C> {
+      using super_type = B;
+      uint16_t c       = {3};
+   };
 
-   .. enumerator:: ATOMIC
+   ext::FieldId<A, atomic<uint16_t>> ext_a_1;
+   ext::FieldId<C, uint16_t> ext_c_1;
 
-      Represents atomic field reference (read, write or other atomic operation).
+   C &x = *(ext::alloc<C>());
+   ext::viewFormat(x);
 
-   .. enumerator:: BIT
+:func:`viewFormat` prints a diagram of the position and size of bytes used within the allocated memory.
+::
 
-      Represents compressed boolean fields.
+   1A | EXT  | 2b | ##________##__
+   1A | BASE | 2b | __##__________
+   1B | BASE | 2b | ____##________
+   1C | EXT  | 2b | ______##____##
+   1C | BASE | 2b | ________##____
 
-   .. enumerator:: STATIC
 
-      Represents immutable field, value is not expected to change, no internal thread safety.
 
-   .. enumerator:: ACIDPTR
+See src/tscore/unit_tests/test_Extendible.cc for more examples.
 
-      Represents a pointer promising Atomicity, Consistency, Isolation, Durability
+Reference
++++++++++
 
-      .. seealso:: :ref:`AcidPtr`
+Namespace `ext`
 
-   .. enumerator:: DIRECT
+.. class:: template<typename Derived_t, typename Field_t> FieldId
 
-      Represents a mutable field with no internal thread safety.
+   The handle used to access a field. These are templated to prevent human error, and branching logic.
 
-   .. enumerator:: C_API
+   :tparam Derived_t: The class that you want to extend at runtime.
+   :tparam Field_t: The type of the field.
+
+.. type:: const void* ExtFieldContext
+
+   The handle used to access a field through C API. Human error not allowed by convention.
 
-      Represents C-Style pointer access.
 
 .. class:: template<typename Derived_t> Extendible
 
-   Allocates block of memory, uses |FieldId| or schema to access slices of memory.
+   Allocates block of memory, uses |FieldId| and |Schema| to access slices of memory.
 
    :tparam Derived_t: The class that you want to extend at runtime.
 
-   .. class:: template<AccessEnum FieldAccess_e, typename T> FieldId
+   .. member:: static Schema  schema
 
-      The handle used to access a field. These are templated to prevent human error, and branching logic.
+      one schema instance per |Extendible| to define contained |FieldDesc|
 
-      :tparam FieldAccess_e: The type of access to the field.
-      :tparam T: The type of the field.
 
-   .. class:: FieldSchema
+.. function:: template<typename Derived_t> Extendible* alloc()
 
-      Stores attributes, constructor and destructor of a field.
+   Allocate a block of memory. Construct the base data.
+   Recursively construct and initialize `Derived_t::super_type` and its |Extendible| classes.
 
-   .. class:: Schema
+   :tparam Derived_t: The Derived class to allocate.
 
-      Manages fields and memory layout of an |Extendible| type.
+.. function:: template<typename Derived_t, typename Field_t> \
+   bool fieldAdd(FieldId\<Derived_t, Field_t> & field_id, std::string const & field_name)
 
-      .. function:: template<AccessEnum FieldAccess_e, typename T> bool addField(FieldId<FieldAccess_e, T> & field_id, std::string const & field_name)
+   Declare a new |FieldId| for Derived_t.
 
-         Add a new field to this record type.
+   :tparam Derived_t: The class that uses this field.
+   :tparam Field_t: The type of the field.
 
-   .. member:: static Schema schema
+.. function:: template<typename Derived_t, typename Field_t> \
+   bool fieldFind(FieldId\<Derived_t, Field_t> & field_id, std::string const & field_name)
 
-      one schema instance per |Extendible| to define contained fields
+   Find an existing |FieldId| for Derived_t.
 
-   .. function:: template<AccessEnum FieldAccess_e, typename T> std::atomic<T> & get(FieldId<AccessEnum::ATOMIC, T> const & field)
+   :tparam Derived_t: The class that uses this field.
+   :tparam Field_t: The type of the field.
 
-   .. type::  BitFieldId = FieldId<AccessEnum::BIT, bool>
+.. function:: template<typename T, typename Derived_t,  typename Field_t> \
+   auto const get(T const &, FieldId\<Derived_t,Field_t>)
 
-   .. function:: bool const get(BitFieldId field) const
+   Returns T const& value from the field stored in the |Extendible| allocation.
 
-   .. function:: bool const readBit(BitFieldId field) const
+   :tparam T: The class passed in.
+   :tparam Derived_t: The class that uses this field.
+   :tparam Field_t: The type of the field.
 
-   .. function:: void writeBit(BitFieldId field, bool const val)
+.. function:: template<typename T, typename Derived_t, typename Field_t> \
+   auto set(T &, FieldId\<Derived_t,Field_t>)
 
-   .. function:: template <AccessEnum FieldAccess_e, typename T> T const & get(FieldId<AccessEnum::STATIC, T> field) const
+   Returns T & value from the field stored in |Extendible| allocation.
 
-   .. function:: template <AccessEnum FieldAccess_e, typename T> T &init(FieldId<AccessEnum::STATIC, T> field)
+   :tparam T: The class passed in.
+   :tparam Derived_t: The class that uses this field.
+   :tparam Field_t: The type of the field.
 
-   .. function:: template <AccessEnum FieldAccess_e, typename T> std::shared_ptr<const T> get(FieldId<AccessEnum::ACIDPTR, T> field) const
+.. function:: template<typename Derived_t> size_t sizeOf()
 
-   .. function:: template <AccessEnum FieldAccess_e, typename T> AcidCommitPtr<T> writeAcidPtr(FieldId<AccessEnum::ACIDPTR, T> field)
+   Recurse through super classes and sum memory needed for allocation.
 
-   .. function:: void * get(FieldId_C & field)
+   Depends on usage of `super_type` in each class.
 
-   .. function template <AccessEnum FieldAccess_e, typename T> auto operator[](FieldId<FieldAccess_e, T> field)
+   :tparam Derived_t: The class to measure.
 
-      :return: the result of :function:`get`
+.. function:: template<typename Derived_t> void viewFormat()
 
+   Recurse through super classes and prints chart of bytes used within the allocation.
+
+   Depends on usage of `super_type` in each class.
+
+   :tparam Derived_t: The class to analyse.
+
+.. function:: template <typename T> std::string toString(T const &t)
+
+   Convert all extendible fields to std::strings (in a YAML-like format) using the :func:`serializeField()`
+
+   :tparam Derived_t: The class to convert to string.
+
+.. function:: template <typename Field_t> void serializeField(std::ostream &os, Field_t const &f)
+
+   Converts a single field into a std::string (in a YAML-like format).
+
+   Specialize this template or overload the `operator<<` for your field to convert properly.
+
+   This is very useful when debugging.
+
+   :tparam Derived_t: The field data type.
+
+Namespace `ext::details`
+
+
+.. class:: FieldDesc
+
+   Defines a span of memory within the allocation, and holds the constructor, destructor and serializer as std::functions.
+
+   Effectively the type-erased version of |FieldId|.
+
+.. class:: Schema
+
+   Manages a memory layout through a map of |FieldDesc|.
 
-.. type:: const void* FieldId_C
 
-   The handle used to access a field through C API. Human error not allowed by convention.
 
diff --git a/include/tscore/Extendible.h b/include/tscore/Extendible.h
index add2f91..6e2d306 100644
--- a/include/tscore/Extendible.h
+++ b/include/tscore/Extendible.h
@@ -23,10 +23,11 @@
   @section details Details
 
 ////////////////////////////////////////////
-  Implements Extendible<Derived_t>:
-  * Extendible<Derived_t>::Schema
-  * Extendible<Derived_t>::FieldSchema
-  * Extendible<Derived_t>::FieldId<Access_t,Field_t>
+  Implements:
+  * Extendible<Derived_t>
+  * Schema
+  * fieldAdd
+  * fieldFind
  */
 
 #pragma once
@@ -34,564 +35,728 @@
 #include <typeinfo>
 #include <typeindex>
 #include <cstddef>
-#include <atomic>
 #include <cstring>
-#include <unordered_map>
+#include <sstream>
+#include <functional>
+#include <iomanip>
+#include <ostream>
+#include <typeindex>
+#include <typeinfo>
 #include <type_traits>
+#include <unordered_map>
 
-#include "tscore/ink_memory.h"
+#include "tscore/AtomicBit.h"
 #include "tscore/ink_assert.h"
-#include "AcidPtr.h"
-
-#ifndef ROUNDUP
-#define ROUNDUP(x, y) ((((x) + ((y)-1)) / (y)) * (y))
-#endif
+#include "tscore/ink_memory.h"
+#include "tscore/ink_defs.h"
 
-// used for C API
-typedef const void *FieldId_C;
+//////////////////////////////////////////
+/// HELPER CLASSES
 
-namespace MT // (multi-threaded)
+/////////////////////////
+// Has 'const_iterator' trait class
+template <typename T> class has_const_iterator
 {
-/// used to store byte offsets to fields
-using ExtendibleOffset_t = uint16_t;
-/// all types must allow unblocking MT read access
-enum AccessEnum { ATOMIC, BIT, STATIC, ACIDPTR, DIRECT, C_API, NUM_ACCESS_TYPES };
+  template <typename U> static std::true_type check(typename U::const_iterator *);
+  template <typename U> static std::false_type check(...);
 
-inline bool &
-areStaticsFrozen()
+public:
+  static constexpr decltype(check<T>(nullptr)) value{}; // value is 'constexpr true' if T defines 'const_iterator'
+};
+/////////////////////////
+// Has 'super_type' trait class
+template <typename T> class has_super_type
 {
-  static bool frozen = false;
-  return frozen;
-}
-
-/**
- * @brief Allows code (and Plugins) to declare member variables during system init.
- *
- * The size of this structure is actually zero, so it will not change the size of your derived class.
- * But new and delete are overridden to use allocate enough bytes of the derived type + added fields.
- * All bool's are packed to save space using the *Bit methods.
- * This API is focused on thread safe data types that allow minimally blocked reading.
- * This is templated so static variables are instanced per Derived type. B/c we need to have different
- * size field sets.
- *
- * @tparam Derived_t - the class that you want to extend at runtime.
- *
- * @see test_Extendible.cc for examples
- *
- */
-template <typename Derived_t> struct Extendible {
-  //////////////////////////////////////////
-  // Internal classes
-  template <AccessEnum FieldAccess_e, typename Field_t> class FieldId; // field handle (strongly type)
-  struct FieldSchema;                                                  // field descriptor
-  class Schema;                                                        // memory layout, field container
-  // aliases
-  using BitFieldId = FieldId<BIT, bool>;
+  template <typename U> static std::true_type check(typename U::super_type *);
+  template <typename U> static std::false_type check(...);
 
-  //////////////////////////////////////////
-  // Extendible static data
-  /// one schema instance per Derived_t to define contained fields
-  static Schema schema;
-
-  //////////////////////////////////////////
-  // Extendible member variables
+public:
+  static constexpr decltype(check<T>(nullptr)) value{}; // value is 'constexpr true' if T defines 'super_type'
+};
 
-  // none
-  // this uses a single alloc and type erasing
+//////////////////////////////////////////
+//////////////////////////////////////////
+//////////////////////////////////////////
 
-  //////////////////////////////////////////
-  // Extendible lifetime management
-  /** don't allow copy construct, that doesn't allow atomicity */
-  Extendible(Extendible &) = delete;
-  /** allocate a new object with additional field data */
-  void *operator new(size_t size);
-  /** free the object */
-  void operator delete(void *ptr);
-  /** construct all fields */
-  Extendible() { schema.call_construct(this_as_char_ptr()); }
-  /** destruct all fields */
-  ~Extendible() { schema.call_destruct(this_as_char_ptr()); }
+// C API
 
-  //////////////////////////////////////////
-  /// Extendible member methods
+// context (internally FieldDesc*)
+typedef void const *ExtFieldContext;
+typedef void *DerivedPtr;
+typedef void *FieldPtr;
 
-  /// ATOMIC API - atomic field reference (read, write or other atomic operation)
-  template <typename Field_t> std::atomic<Field_t> &get(FieldId<ATOMIC, Field_t> const &field);
+FieldPtr ExtFieldPtr(DerivedPtr derived, ExtFieldContext field_context, int *size = nullptr);
 
-  /// BIT API - compressed boolean fields
-  bool const get(BitFieldId field) const;
-  bool const readBit(BitFieldId field) const;
-  void writeBit(BitFieldId field, bool const val);
+//////////////////////////////////////////
+//////////////////////////////////////////
+//////////////////////////////////////////
 
-  /// STATIC API - immutable field, value is not expected to change, no internal thread safety
-  template <typename Field_t> Field_t const &get(FieldId<STATIC, Field_t> field) const;
-  template <typename Field_t> Field_t &init(FieldId<STATIC, Field_t> field);
+namespace ext
+{
+////////////////////////////////////////////////////
+// Forward declarations
+template <typename Derived_t, typename Field_t> class FieldId; // field handle (strongly type)
+template <typename Derived_t> class Extendible;
 
-  /// ACIDPTR API - returns a const shared pointer to last committed field value
-  template <typename Field_t> std::shared_ptr<const Field_t> get(FieldId<ACIDPTR, Field_t> field) const;
-  template <typename Field_t> AcidCommitPtr<Field_t> writeAcidPtr(FieldId<ACIDPTR, Field_t> field);
+namespace details // internal stuff
+{
+  using Offest_t = uint16_t; // used to store byte offsets to fields
+  struct FieldDesc;          // keeps the field properties, and methods
+  class Schema;              // container of fields and methods
 
-  /// DIRECT API -  mutable field, no internal thread safety, expected to be performed externally.
-  template <typename Field_t> Field_t const &get(FieldId<DIRECT, Field_t> field) const;
-  template <typename Field_t> Field_t &get(FieldId<DIRECT, Field_t> field);
+  // forward declare friend functions used outside of details
+  template <typename T> uintptr_t initRecurseSuper(T &, uintptr_t);
+  template <typename T> FieldPtr FieldPtrGet(Extendible<T> const &, details::FieldDesc const &);
 
-  /// C API - returns pointer, no internal thread safety
-  void *get(FieldId_C &field);
+  //////////////////////////////////////////
 
-  // operator[]
-  template <AccessEnum Access_t, typename Field_t> auto operator[](FieldId<Access_t, Field_t> field) { return get(field); }
+  bool &
+  areFieldsFinalized()
+  {
+    static bool finalized = false;
+    return finalized;
+  }
 
   /////////////////////////////////////////////////////////////////////
-  /// defines a runtime "member variable", element of the blob
-  struct FieldSchema {
-    using Func_t = std::function<void(void *)>;
-
-    AccessEnum access         = NUM_ACCESS_TYPES;              ///< which API is used to access the data
-    std::type_index type      = std::type_index(typeid(void)); ///< datatype
-    ExtendibleOffset_t size   = 0;                             ///< size of field
-    ExtendibleOffset_t offset = 0;                             ///< offset of field from 'this'
-    Func_t construct_fn       = nullptr;                       ///< the data type's constructor
-    Func_t destruct_fn        = nullptr;                       ///< the data type's destructor
+  /// ext::details::FieldDesc - type erased field descriptor, with type specific std::functions
+  struct FieldDesc {
+    Offest_t ext_loc_offset;        ///< byte offset to Extendible._ext_loc
+    Offest_t field_offset;          ///< byte offset from ext_loc to field
+    std::type_index field_type_idx; ///< data type index
+    uint16_t size;                  ///< byte size of field
+    uint8_t align;                  ///< alignment of field
+    uint8_t mask;                   ///< mask for packed bit operations
+
+    // specialize the following
+    std::function<void(FieldPtr)> constructor;
+    std::function<void(FieldPtr)> destructor;
+    std::function<void(std::ostream &, void const *)> serializer;
+
+    FieldDesc() : field_type_idx(typeid(std::nullptr_t)) {}
   };
 
   /////////////////////////////////////////////////////////////////////
-  /// manages the a static layout of fields as data structures
+  /// ext::details::Schema manages the a static layout of fields as data structures
   class Schema
   {
-    friend Extendible; // allow direct access for internal classes
-
-  private:
-    std::unordered_map<std::string, FieldSchema> fields;  ///< defined elements of the blob by name
-    uint32_t bit_offset             = 0;                  ///< offset to first bit
-    size_t alloc_size               = sizeof(Derived_t);  ///< bytes to allocate
-    size_t alloc_align              = alignof(Derived_t); ///< alignment for each allocation
-    std::atomic_uint instance_count = {0};                ///< the number of Extendible<Derived> instances in use.
+  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.
 
   public:
     Schema() {}
-
-    /// Add a new Field to this record type
-    template <AccessEnum Access_t, typename Field_t>
-    bool addField(FieldId<Access_t, Field_t> &field_id, std::string const &field_name);
-
-    template <typename Field_t> bool addField(FieldId<ACIDPTR, Field_t> &field_id, std::string const &field_name);
-    template <AccessEnum Access_t, typename Field_t> class FieldId<Access_t, Field_t> find(std::string const &field_name);
-
-    /// Add a new Field to this record type (for a C API)
-
-    FieldId_C addField_C(char const *field_name, size_t size, void (*construct_fn)(void *), void (*destruct_fn)(void *));
-    FieldId_C find_C(char const *field_name); ///< C_API returns an existing fieldId
+    ~Schema();
 
     /// Testing methods
-    size_t size() const;       ///< returns sizeof memory allocated
     bool no_instances() const; ///< returns true if there are no instances of Extendible<Derived_t>
     bool reset();              ///< clears all field definitions.
 
-  protected:
-    /// Internal methods
-    void updateMemOffsets();                    ///< updates memory offsets, alignment, and total allocation size
-    void call_construct(char *ext_as_char_ptr); ///< calls constructor for each field
-    void call_destruct(char *ext_as_char_ptr);  ///< call destructor for each field
+    size_t fullSize(size_t base_size) const;       ///< returns sizeof memory allocated
+    void updateMemOffsets();                       ///< updates memory offsets, alignment, and total allocation size
+    void callConstructor(uintptr_t ext_start_ptr); ///< calls constructor for each field
+    void callDestructor(uintptr_t ext_start_ptr);  ///< call destructor for each field
 
   }; // end Schema struct
+} // namespace details
+
+/// 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
+ * 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.
+ *
+ * @tparam Derived_t - the class that you want to extend at runtime.
+ *
+ * @see test_Extendible.cc for examples
+ *
+ */
+template <typename Derived_t> class Extendible
+{
+public:
+  using short_ptr_t = uint16_t;
+
+  // static
+  static details::Schema schema; ///< one schema instance per Derived_t to define contained fields
 
+  // return the address offset of the ext_loc member variable
+  static constexpr size_t
+  getLocOffset()
+  {
+    return offsetof(Extendible<Derived_t>, ext_loc);
+  }
+
+  // member variables
 private:
-  // Extendible convenience methods
-  char *this_as_char_ptr();
-  char const *this_as_char_ptr() const;
+  short_ptr_t ext_loc = 0; ///< byte offset to extendible storage
 
-  template <typename Return_t> Return_t at_offset(ExtendibleOffset_t offset);
-  template <typename Return_t> Return_t at_offset(ExtendibleOffset_t offset) const;
-};
+  // lifetime management
+  /** don't allow copy construct, that doesn't allow atomicity */
+public:
+  Extendible(Extendible &) = delete;
 
-// define the static schema per derived type
-template <typename Derived_t> typename Extendible<Derived_t>::Schema Extendible<Derived_t>::schema;
+protected:
+  Extendible();
+  // use ext::alloc() exclusively for allocation and initialization
 
-/////////////////////////////////////////////////////////////////////
-// class FieldId
-//
-/// strongly type the FieldId to avoid user error and branching logic
-template <typename Derived_t> template <AccessEnum FieldAccess_e, typename Field_t> class Extendible<Derived_t>::FieldId
-{
-  friend Extendible; // allow direct access for internal classes
+  /** destruct all fields */
+  ~Extendible() { schema.callDestructor(uintptr_t(this) + ext_loc); }
 
 private:
-  ExtendibleOffset_t const *offset_ptr = nullptr;
+  /** construct all fields */
+  size_t initFields(uintptr_t start_ptr); ///< tell this extendible where it's memory offset start is.
 
-public:
-  FieldId() {}
-  bool
-  isValid() const
+  uintptr_t
+  getBegin() const
   {
-    return offset_ptr;
+    return uintptr_t(this) + ext_loc;
   }
 
-private:
-  FieldId(ExtendibleOffset_t const &offset) { offset_ptr = &offset; }
-  ExtendibleOffset_t
-  offset() const
-  {
-    return *offset_ptr;
-  }
+  template <typename T> friend T *alloc();
+  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);
 };
 
-////////////////////////////////////////////////////
-// Extendible::Schema Method Definitions
-//
+// define the static schema per derived type
+template <typename Derived_t> details::Schema Extendible<Derived_t>::schema;
 
-/// Add a new Field to this record type
-template <typename Derived_t>
-template <AccessEnum Access_t, typename Field_t>
-bool
-Extendible<Derived_t>::Schema::addField(FieldId<Access_t, Field_t> &field_id, std::string const &field_name)
-{
-  static_assert(Access_t == BIT || std::is_same<Field_t, bool>::value == false,
-                "Use BitField so we can pack bits, they are still atomic.");
-  ink_release_assert(instance_count == 0); // it's too late, we already started allocating.
-
-  ExtendibleOffset_t size = 0;
-  switch (Access_t) {
-  case BIT: {
-    size = 0;
-  } break;
-  case ATOMIC: {
-    size        = std::max(sizeof(std::atomic<Field_t>), alignof(std::atomic<Field_t>));
-    alloc_align = std::max(alloc_align, alignof(std::atomic<Field_t>));
-  } break;
-  default:
-    size = sizeof(Field_t);
-  }
+//####################################################
+//####################################################
+// UTILITY Functions
 
-  // capture the default constructors of the data type
-  static auto construct_fn = [](void *ptr) { new (ptr) Field_t; };
-  static auto destruct_fn  = [](void *ptr) { static_cast<Field_t *>(ptr)->~Field_t(); };
+//////////////////////////////////////////////////////
+/// HexToString function for serializing untyped C storage
+// TODO: use ts::bwf::As_Hex() after PR goes through
+inline void
+hexToStream(std::ostream &os, void const *buf, uint16_t size)
+{
+  static const char hexDigits[] = "0123456789abcdef";
 
-  fields[field_name]  = FieldSchema{Access_t, std::type_index(typeid(Field_t)), size, 0, construct_fn, destruct_fn};
-  field_id.offset_ptr = &fields[field_name].offset;
-  updateMemOffsets();
-  return true;
-}
+  const uint8_t *src = static_cast<const uint8_t *>(buf);
+  for (int i = 0; i < size; i++) {
+    os << hexDigits[src[i]];
+  }
+};
 
-/// Add a new Field to this record type
-template <typename Derived_t>
 template <typename Field_t>
-bool
-Extendible<Derived_t>::Schema::addField(FieldId<ACIDPTR, Field_t> &field_id, std::string const &field_name)
+void
+serializeField(std::ostream &os, Field_t const &f)
 {
-  static_assert(std::is_copy_constructible<Field_t>::value == true, "Must have a copy constructor to use AcidPtr.");
-  ink_release_assert(instance_count == 0); // it's too late, we already started allocating.
-  using ptr_t             = AcidPtr<Field_t>;
-  ExtendibleOffset_t size = sizeof(ptr_t);
-
-  // capture the default constructors of the data type
-  static auto construct_fn = [](void *ptr) { new (ptr) ptr_t(new Field_t()); };
-  static auto destruct_fn  = [](void *ptr) { static_cast<ptr_t *>(ptr)->~ptr_t(); };
-
-  fields[field_name]  = FieldSchema{ACIDPTR, std::type_index(typeid(Field_t)), size, 0, construct_fn, destruct_fn};
-  field_id.offset_ptr = &fields[field_name].offset;
-  updateMemOffsets();
-  return true;
+  using namespace std;
+  // print containers as lists
+  if constexpr (has_const_iterator<Field_t>::value) {
+    os << "[";
+    for (auto const &a : f) {
+      serializeField(os, a);
+      os << ", ";
+    }
+    os << "]";
+    return;
+  } else {
+    os << f;
+  }
 }
+//####################################################
+//####################################################
+//####################################################
 
-/// Add a new Field to this record type (for a C API)
-template <typename Derived_t>
-FieldId_C
-Extendible<Derived_t>::Schema::addField_C(char const *field_name, size_t size, void (*construct_fn)(void *),
-                                          void (*destruct_fn)(void *))
+/////////////////////////////////////////////////////////////////////
+// FieldId
+/// a strongly typed pointer to FieldDesc
+template <typename Derived_t, typename Field_t> class FieldId
 {
-  ink_release_assert(size == 1 || size == 2 || size == 4 || size % 8 == 0); // must use aligned sizes
-  ink_release_assert(instance_count == 0);                                  // it's too late, we already started allocating.
-  const std::string field_name_str(field_name);
-  fields[field_name_str] =
-    FieldSchema{C_API, std::type_index(typeid(void *)), static_cast<ExtendibleOffset_t>(size), 0, construct_fn, destruct_fn};
-  updateMemOffsets();
-  return &fields[field_name].offset;
-}
+public:
+  ext::details::FieldDesc const *desc = nullptr;
+  bool isValid() const;
+  FieldId(ext::details::FieldDesc const &);
+  FieldId() {}
+  FieldId(FieldId const &) = default;
+};
 
-template <typename Derived_t>
-template <AccessEnum Access_t, typename Field_t>
-class Extendible<Derived_t>::FieldId<Access_t, Field_t>
-Extendible<Derived_t>::Schema::find(std::string const &field_name)
+namespace details
 {
-  auto field_iter = fields.find(field_name);
-  if (field_iter == fields.end()) {
-    return Extendible<Derived_t>::FieldId<Access_t, Field_t>(); // didn't find name
+  template <typename Derived_t>
+  FieldPtr
+  FieldPtrGet(Extendible<Derived_t> const &d, FieldDesc const &desc)
+  {
+    return FieldPtr(d.Extendible<Derived_t>::getBegin() + desc.field_offset);
   }
-  FieldSchema &fs = field_iter->second;                    // found name
-  ink_assert(fs.access == Access_t);                       // conflicting access, between field add and find
-  ink_assert(fs.type == std::type_index(typeid(Field_t))); // conflicting type, between field add and find
-  return Extendible<Derived_t>::FieldId<Access_t, Field_t>(fs.offset);
-}
 
-template <typename Derived_t>
-FieldId_C
-Extendible<Derived_t>::Schema::find_C(char const *field_name)
-{
-  auto field_iter = fields.find(field_name);
-  ink_release_assert(field_iter != fields.end());
-  FieldSchema &fs = field_iter->second;
-  ink_release_assert(fs.access == C_API);
-  return &fs.offset;
-}
+  // overloadable function to construct and init an FieldId
+  //////////////////////////////////////////////////////
+  /// Type Generic Template
 
-template <typename Derived_t>
-void
-Extendible<Derived_t>::Schema::updateMemOffsets()
-{
-  ink_release_assert(instance_count == 0);
+  template <typename Derived_t, typename Field_t>
+  Field_t const &
+  fieldGet(void const *fld_ptr, FieldId<Derived_t, Field_t> const &field)
+  {
+    return *static_cast<Field_t const *>(fld_ptr);
+  }
 
-  uint32_t acc_offset = ROUNDUP(sizeof(Derived_t), alloc_align);
+  template <typename Derived_t, typename Field_t>
+  Field_t &
+  fieldSet(void *fld_ptr, FieldId<Derived_t, Field_t> const &field)
+  {
+    return *static_cast<Field_t *>(fld_ptr);
+  }
 
-  ExtendibleOffset_t size_blocks[] = {4, 2, 1};
+  template <typename Derived_t, typename Field_t>
+  void
+  makeFieldId(FieldId<Derived_t, Field_t> &id, FieldDesc &desc)
+  {
+    ink_assert(!areFieldsFinalized());
 
-  for (auto &pair_fld : fields) {
-    auto &fld = pair_fld.second;
-    if (fld.size >= 8) {
-      fld.offset = acc_offset;
-      acc_offset += fld.size;
-    }
+    desc.field_type_idx = std::type_index(typeid(Field_t));
+    desc.ext_loc_offset = Extendible<Derived_t>::getLocOffset();
+    desc.field_offset   = std::numeric_limits<decltype(desc.field_offset)>::max();
+    desc.size           = sizeof(Field_t);
+    desc.align          = alignof(Field_t);
+    desc.mask           = 0;
+
+    id = FieldId<Derived_t, Field_t>(desc);
+
+    //
+    desc.constructor = [](FieldPtr fld_ptr) { new (fld_ptr) Field_t(); };
+    desc.destructor  = [](FieldPtr fld_ptr) { static_cast<Field_t *>(fld_ptr)->~Field_t(); };
+    desc.serializer  = [id](std::ostream &os, void const *fld_ptr) { serializeField(os, fieldGet(fld_ptr, id)); };
   }
-  for (auto sz : size_blocks) {
-    for (auto &pair_fld : fields) {
-      auto &fld = pair_fld.second;
-      if (fld.size == sz) {
-        fld.offset = acc_offset;
-        acc_offset += fld.size;
-      }
-    }
+
+  //////////////////////////////////////////////////////
+  /// C API specialization
+  // no type or constructor. Just a size.
+
+  template <typename Derived_t>
+  void
+  makeFieldId(FieldDesc &desc, uint16_t size)
+  {
+    ink_assert(!areFieldsFinalized());
+    desc.field_type_idx = typeid(void);
+    desc.ext_loc_offset = Extendible<Derived_t>::getLocOffset();
+    desc.field_offset   = std::numeric_limits<decltype(desc.field_offset)>::max();
+    desc.size           = size;
+    desc.align          = 1;
+    desc.mask           = 0;
+
+    //
+    desc.constructor = nullptr;
+    desc.destructor  = nullptr;
+    desc.serializer  = [size](std::ostream &os, void const *fld_ptr) { hexToStream(os, fld_ptr, size); };
   }
-  bit_offset              = acc_offset;
-  uint32_t acc_bit_offset = 0;
-  for (auto &pair_fld : fields) {
-    auto &fld = pair_fld.second;
-    if (fld.size == 0) {
-      fld.offset = acc_bit_offset;
-      ++acc_bit_offset;
-    }
+
+  //////////////////////////////////////////////////////
+  /// Bool specializations
+
+  template <typename Derived_t>
+  const bool
+  fieldGet(const void *fld_ptr, FieldId<Derived_t, bool> const &field)
+  {
+    return bool((*static_cast<const uint8_t *>(fld_ptr)) & field.desc->mask);
   }
 
-  alloc_size = acc_offset + (acc_bit_offset + 7) / 8; // size '0' are packed bit allocations.
-}
+  template <typename Derived_t>
+  AtomicBit
+  fieldSet(FieldPtr fld_ptr, FieldId<Derived_t, bool> const &field)
+  {
+    return AtomicBit{static_cast<uint8_t *>(fld_ptr), field.desc->mask};
+  }
 
-template <typename Derived_t>
-bool
-Extendible<Derived_t>::Schema::reset()
-{
-  if (instance_count > 0) {
-    // free instances before calling this so we don't leak memory
-    return false;
+  template <typename Derived_t>
+  void
+  makeFieldId(FieldId<Derived_t, bool> &id, FieldDesc &desc)
+  {
+    desc.field_type_idx = std::type_index(typeid(bool));
+    desc.ext_loc_offset = Extendible<Derived_t>::getLocOffset();
+    desc.field_offset   = std::numeric_limits<decltype(desc.field_offset)>::max();
+    desc.size           = 0;
+    desc.align          = 0;
+    desc.mask           = 0;
+
+    id = FieldId<Derived_t, bool>(desc);
+
+    //
+    desc.constructor = nullptr;
+    desc.destructor  = nullptr;
+    desc.serializer  = [id](std::ostream &os, void const *fld_ptr) { serializeField(os, fieldGet(fld_ptr, id)); };
   }
-  fields.clear();
-  alloc_size  = sizeof(Derived_t);
-  alloc_align = alignof(Derived_t);
-  return true;
-}
 
-template <typename Derived_t>
-void
-Extendible<Derived_t>::Schema::call_construct(char *ext_as_char_ptr)
-{
-  ++instance_count; // don't allow schema modification
-  // init all extendible memory to 0, in case constructors don't
-  memset(ext_as_char_ptr + sizeof(Derived_t), 0, alloc_size - sizeof(Derived_t));
-
-  for (auto const &elm : fields) {
-    FieldSchema const &field_schema = elm.second;
-    if (field_schema.access != BIT && field_schema.construct_fn != nullptr) {
-      field_schema.construct_fn(ext_as_char_ptr + field_schema.offset);
-    }
+  //////////////////////////////////////////////////////
+  /// std::atomic<bool> specializations (same as bool)
+
+  template <typename Derived_t>
+  inline const bool
+  fieldGet(void const *fld_ptr, FieldId<Derived_t, std::atomic<bool>> const &field)
+  {
+    return bool(fld_ptr & field.mask);
   }
-}
 
-template <typename Derived_t>
-void
-Extendible<Derived_t>::Schema::call_destruct(char *ext_as_char_ptr)
-{
-  for (auto const &elm : fields) {
-    FieldSchema const &field_schema = elm.second;
-    if (field_schema.access != BIT && field_schema.destruct_fn != nullptr) {
-      field_schema.destruct_fn(ext_as_char_ptr + field_schema.offset);
-    }
+  template <typename Derived_t>
+  inline AtomicBit
+  fieldSet(FieldPtr fld_ptr, FieldId<Derived_t, std::atomic<bool>> const &field)
+  {
+    return AtomicBit{fld_ptr, field.mask};
   }
-  --instance_count;
-}
 
-template <typename Derived_t>
-size_t
-Extendible<Derived_t>::Schema::size() const
+  template <typename Derived_t>
+  void
+  makeFieldId(FieldId<Derived_t, std::atomic<bool>> &id, FieldDesc &desc)
+  {
+    desc.field_type_idx = std::type_index(typeid(std::atomic<bool>));
+    desc.ext_loc_offset = Extendible<Derived_t>::getLocOffset();
+    desc.field_offset   = std::numeric_limits<decltype(desc.field_offset)>::max();
+    desc.size           = 0;
+    desc.align          = 0;
+    desc.mask           = 0;
+
+    id = FieldId<Derived_t, std::atomic<bool>>(desc);
+
+    //
+    desc.constructor = nullptr;
+    desc.destructor  = nullptr;
+    desc.serializer  = [id](std::ostream &os, void const *fld_ptr) { serializeField(os, fieldGet(fld_ptr, id)); };
+  }
+} // namespace details
+
+//////////////////////////////////////////////////////
+/// safely cast FieldDesc back to FieldId
+
+template <typename Derived_t, typename Field_t>
+FieldId<Derived_t, Field_t>::FieldId(ext::details::FieldDesc const &fld_desc) : desc(&fld_desc)
 {
-  return alloc_size;
+  const size_t loc = Extendible<Derived_t>::getLocOffset();
+  ink_assert(loc == fld_desc.ext_loc_offset);
+  ink_assert(std::type_index(typeid(Field_t)) == fld_desc.field_type_idx);
 }
 
-template <typename Derived_t>
+template <typename Derived_t, typename Field_t>
 bool
-Extendible<Derived_t>::Schema::no_instances() const
+FieldId<Derived_t, Field_t>::isValid() const
 {
-  return instance_count == 0;
+  return desc != nullptr;
 }
 
+//####################################################
+//####################################################
+//####################################################
+// Functional API for Extendible Field Access
+//
+
 ////////////////////////////////////////////////////
-// Extendible Method Definitions
+// Schema Method Definitions
 //
 
-/// return a reference to an atomic field (read, write or other atomic operation)
-template <typename Derived_t>
-template <typename Field_t>
-std::atomic<Field_t> &
-Extendible<Derived_t>::get(FieldId<ATOMIC, Field_t> const &field)
+/// Add a new Field to this record type
+template <class Derived_t, class Field_t>
+bool
+fieldAdd(FieldId<Derived_t, Field_t> &field_id, char const *field_name)
 {
-  return *at_offset<std::atomic<Field_t> *>(field.offset());
-}
+  using namespace ext::details;
+  Schema &schema = Extendible<Derived_t>::schema;
+  ink_release_assert(schema.no_instances()); // it's too late, we already started allocating.
+  ink_release_assert(!areFieldsFinalized()); // it's too late, Fields must be added during Plugin Init.
 
-/// atomically read a bit value
-template <typename Derived_t>
-bool const
-Extendible<Derived_t>::get(BitFieldId field) const
-{
-  return readBit(field);
-}
+  auto field_iter = schema.fields.find(field_name);
+  if (field_iter != schema.fields.end()) {
+    return false;
+  }
 
-/// atomically read a bit value
-template <typename Derived_t>
-bool const
-Extendible<Derived_t>::readBit(BitFieldId field) const
-{
-  const char &c   = *(at_offset<char const *>(schema.bit_offset) + field.offset() / 8);
-  const char mask = 1 << (field.offset() % 8);
-  return (c & mask) != 0;
+  makeFieldId(field_id, schema.fields[field_name]);
+  schema.updateMemOffsets();
+  return true;
 }
 
-/// atomically write a bit value
-template <typename Derived_t>
-void
-Extendible<Derived_t>::writeBit(BitFieldId field, bool const val)
+/// Add a new Field to Derived_t, this C function uses a fat API
+template <class Derived_t>
+ExtFieldContext
+fieldAdd(char const *field_name, int size, void (*construct_fn)(void *), void (*destruct_fn)(void *))
 {
-  char &c         = *(at_offset<char *>(schema.bit_offset) + field.offset() / 8);
-  const char mask = 1 << (field.offset() % 8);
-  if (val) {
-    c |= mask;
+  using namespace ext::details;
+  Schema &schema = Extendible<Derived_t>::schema;
+  ink_release_assert(schema.no_instances()); // it's too late, we already started allocating.
+  ink_release_assert(!areFieldsFinalized()); // it's too late, Fields must be added during Plugin Init.
+  ink_release_assert(size >= 0);             // non-negative numbers please
+
+  auto field_iter = schema.fields.find(field_name);
+  if (field_iter != schema.fields.end()) {
+    return nullptr;
+  }
+  if (size == 0) {
+    FieldId<Derived_t, bool> id;
+    makeFieldId(id, schema.fields[field_name]);
   } else {
-    c &= ~mask;
+    FieldDesc &desc = schema.fields[field_name];
+    makeFieldId<Derived_t>(desc, size);
+    desc.constructor = construct_fn;
+    desc.destructor  = destruct_fn;
   }
+  schema.updateMemOffsets();
+  return &schema.fields[field_name];
 }
 
-/// return a reference to an const field
-template <typename Derived_t>
-template <typename Field_t>
-Field_t const & // value is not expected to change, or be freed while 'this' exists.
-Extendible<Derived_t>::get(FieldId<STATIC, Field_t> field) const
+template <class Derived_t, class Field_t>
+bool
+fieldFind(FieldId<Derived_t, Field_t> &field_id, char const *field_name)
 {
-  return *at_offset<const Field_t *>(field.offset());
+  using namespace ext::details;
+  ink_release_assert(areFieldsFinalized());
+  Schema const &schema = Extendible<Derived_t>::schema;
+  auto field_iter      = schema.fields.find(field_name);
+  if (field_iter == schema.fields.end()) {
+    return false; // didn't find name
+  }
+  field_id = FieldId<Derived_t, Field_t>(field_iter->second);
+  return true;
 }
 
-/// return a reference to an static field that is non-const for initialization purposes
-template <typename Derived_t>
-template <typename Field_t>
-Field_t &
-Extendible<Derived_t>::init(FieldId<STATIC, Field_t> field)
+// each Derived_t will have a DerivedExtfieldFind
+template <class Derived_t>
+ExtFieldContext
+fieldFind(char const *field_name)
 {
-  ink_release_assert(!areStaticsFrozen());
-  return *at_offset<Field_t *>(field.offset());
+  using namespace ext::details;
+  ink_release_assert(areFieldsFinalized());
+  Schema const &schema = Extendible<Derived_t>::schema;
+  auto field_iter      = schema.fields.find(field_name);
+  if (field_iter == schema.fields.end()) {
+    return nullptr; // didn't find name
+  }
+  return &field_iter->second;
 }
 
-/// return a shared pointer to last committed field value
-template <typename Derived_t>
-template <typename Field_t>
-std::shared_ptr<const Field_t> // shared_ptr so the value can be updated while in use.
-Extendible<Derived_t>::get(FieldId<ACIDPTR, Field_t> field) const
+////////////////////////////////////////////////////
+/// ext::get & ext::set accessor functions
+template <typename T, typename Derived_t, typename Field_t>
+inline decltype(auto)
+get(T const &d, FieldId<Derived_t, Field_t> &field)
 {
-  const AcidPtr<Field_t> &reader = *at_offset<const AcidPtr<Field_t> *>(field.offset());
-  return reader.getPtr();
+  Extendible<Derived_t> const &ext = d;
+  FieldPtr fld_ptr                 = ext::details::FieldPtrGet(ext, *field.desc);
+  return ext::details::fieldGet(fld_ptr, field);
 }
 
-/// return a writer created from the last committed field value
-template <typename Derived_t>
-template <typename Field_t>
-AcidCommitPtr<Field_t>
-Extendible<Derived_t>::writeAcidPtr(FieldId<ACIDPTR, Field_t> field)
+template <typename T, typename Derived_t, typename Field_t>
+inline decltype(auto)
+set(T &d, FieldId<Derived_t, Field_t> &field)
 {
-  AcidPtr<Field_t> &reader = *at_offset<AcidPtr<Field_t> *>(field.offset());
-  return AcidCommitPtr<Field_t>(reader);
+  Extendible<Derived_t> const &ext = d;
+  FieldPtr fld_ptr                 = ext::details::FieldPtrGet(ext, *field.desc);
+  return ext::details::fieldSet(fld_ptr, field);
 }
 
-/// return a reference to a field, without concurrent access protection
-template <typename Derived_t>
-template <typename Field_t>
-Field_t & // value is not expected to change, or be freed while 'this' exists.
-Extendible<Derived_t>::get(FieldId<DIRECT, Field_t> field)
-{
-  return *at_offset<Field_t *>(field.offset());
-}
+/////////////////////////
+// ext::sizeOf - returns the size of a class + all extensions.
+//
 
-/// return a const reference to a field, without concurrent access protection
 template <typename Derived_t>
-template <typename Field_t>
-Field_t const & // value is not expected to change, or be freed while 'this' exists.
-Extendible<Derived_t>::get(FieldId<DIRECT, Field_t> field) const
+inline size_t
+sizeOf(size_t size = sizeof(Derived_t))
 {
-  return *at_offset<const Field_t *>(field.offset());
+  // add size of super extendibles
+  if constexpr (has_super_type<Derived_t>::value) {
+    size = ext::sizeOf<typename Derived_t::super_type>(size);
+  } else {
+    static_assert(std::is_same<decltype(Derived_t::schema), decltype(Extendible<Derived_t>::schema)>::value,
+                  "ambiguous schema, Derived_t is missing super_type");
+  }
+
+  // add size of this extendible
+  if constexpr (std::is_base_of<Extendible<Derived_t>, Derived_t>::value) {
+    size = Extendible<Derived_t>::schema.fullSize(size);
+    // assert that the schema is not ambiguous.
+  }
+  return size;
 }
 
-/// C API
-template <typename Derived_t>
-void *
-Extendible<Derived_t>::get(FieldId_C &field)
+/////////////////////////
+// Ext Alloc & Init
+//
+template <class Derived_t> Extendible<Derived_t>::Extendible()
 {
-  return at_offset<void *>(*static_cast<ExtendibleOffset_t const *>(field));
+  ink_assert(ext::details::areFieldsFinalized());
 }
 
-/// allocate a new object with properties
+/// tell this extendible where it's memory offset start is. Added to support inheriting from extendible classes
 template <class Derived_t>
-void *
-Extendible<Derived_t>::operator new(size_t size)
+uintptr_t
+Extendible<Derived_t>::initFields(uintptr_t start_ptr)
 {
-  // allocate one block for all the memory, including the derived_t members
-  // return ::operator new(schema.alloc_size);
-  void *ptr = ats_memalign(schema.alloc_align, schema.alloc_size);
-  ink_release_assert(ptr != nullptr);
-  return 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);
+  schema.callConstructor(start_ptr);    // construct all fields
+  return start_ptr + schema.alloc_size; // return the end of the extendible data
 }
 
-/// free the object
-template <class Derived_t>
-void
-Extendible<Derived_t>::operator delete(void *ptr)
+namespace details
 {
-  ats_free(ptr);
-  ink_release_assert(ptr != nullptr);
-}
+  /// recursively init all extendible structures, and construct fields
+  template <typename Derived_t>
+  uintptr_t
+  initRecurseSuper(Derived_t &devired, uintptr_t tail_ptr /*= 0*/)
+  {
+    // track a tail pointer, that starts after the class, and interate each extendible block
+    if constexpr (has_super_type<Derived_t>::value) {
+      // init super type, move tail pointer
+      tail_ptr = initRecurseSuper<typename Derived_t::super_type>(devired, tail_ptr);
+    }
+    if constexpr (std::is_base_of<Extendible<Derived_t>, Derived_t>::value) {
+      // set start for this extendible block after the previous extendible, and move tail pointer to after this block
+      tail_ptr = devired.Extendible<Derived_t>::initFields(tail_ptr);
+    }
+    return tail_ptr;
+  }
+} // namespace details
 
-// private
-template <class Derived_t>
-char *
-Extendible<Derived_t>::this_as_char_ptr()
+// allocate and initialize an extendible data structure
+template <typename Derived_t>
+Derived_t *
+alloc()
 {
-  return static_cast<char *>(static_cast<void *>(this));
+  static_assert(std::is_base_of<Extendible<Derived_t>, Derived_t>::value);
+  ink_assert(ext::details::areFieldsFinalized());
+
+  // calculate the memory needed
+  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
+  // construct (recursively super-to-sub class)
+  new (ptr) Derived_t();
+  // define extendible blocks start offsets (recursively super-to-sub class)
+  details::initRecurseSuper(*ptr, uintptr_t(ptr) + sizeof(Derived_t));
+  return ptr;
 }
-// private
-template <class Derived_t>
-char const *
-Extendible<Derived_t>::this_as_char_ptr() const
+
+/////////////////////////
+// ExtDebugFormat - print a ascii chart of memory layout of a class
+//
+//  Example layout of C -> B -> A, where C and A are extendible. All contain 1 int.
+//  See test_Extendible.cc for class implementation.
+//  1A | EXT  | 2b | ##________##__
+//  1A | BASE | 2b | __##__________
+//  1B | BASE | 2b | ____##________
+//  1C | EXT  | 2b | ______##____##
+//  1C | BASE | 2b | ________##____
+
+template <typename T>
+std::string
+viewFormat(T const &t, uintptr_t _base_addr = 0, int _full_size = ext::sizeOf<T>())
 {
-  return static_cast<char const *>(static_cast<void const *>(this));
+  using namespace std;
+  stringstream ss;
+  if (_base_addr == 0) {
+    _base_addr = uintptr_t(&t);
+  }
+  int super_size = 0;
+  if constexpr (has_super_type<T>::value) {
+    ss << viewFormat<typename T::super_type>(t, _base_addr, _full_size);
+    super_size += sizeof(typename T::super_type);
+  }
+
+  if constexpr (is_base_of<Extendible<T>, T>::value) {
+    Extendible<T> const *e = &t;
+    int ptr_start          = uintptr_t(e) + Extendible<T>::getLocOffset() - _base_addr;
+    int ptr_end            = ptr_start + sizeof(typename Extendible<T>::short_ptr_t);
+    int ext_start          = e->getBegin() - _base_addr;
+    int ext_end            = Extendible<T>::schema.fullSize(ext_start);
+
+    ink_assert(ptr_end <= ext_start);
+    ink_assert(ext_end <= _full_size);
+
+    ss << endl << setw(30) << typeid(T).name() << " | EXT  | " << setw(5) << ext_end - ext_start << "b |";
+    ss << string(ptr_start, '_').c_str();
+    ss << string(ptr_end - ptr_start, '#').c_str();
+    ss << string(ext_start - ptr_end, '_').c_str();
+    ss << string(ext_end - ext_start, '#').c_str();
+    ss << string(_full_size - ext_end, '_').c_str();
+
+    super_size += sizeof(Extendible<T>);
+  }
+
+  int super_start  = uintptr_t(&t) - _base_addr;
+  int member_start = super_start + super_size;
+  int member_end   = super_start + sizeof(T);
+
+  ink_assert(member_start <= member_end);
+  ink_assert(member_end <= _full_size);
+
+  ss << endl << setw(30) << typeid(T).name() << " | BASE | " << setw(5) << sizeof(T) - super_size << "b |";
+  ss << string(super_start, '_').c_str();
+  ss << string(member_start - super_start, '_').c_str();
+  ss << string(member_end - member_start, '#').c_str();
+  ss << string(_full_size - member_end, '_').c_str();
+
+  return ss.str();
 }
-// private
-template <class Derived_t>
-template <typename Return_t>
-Return_t
-Extendible<Derived_t>::at_offset(ExtendibleOffset_t offset)
+
+namespace details
 {
-  return reinterpret_cast<Return_t>(this_as_char_ptr() + offset);
+  std::string
+  ltrim(std::string const &str, const std::string &chars = "\t\n\v\f\r ")
+  {
+    std::string r(str);
+    r.erase(0, str.find_first_not_of(chars));
+    return r;
+  }
+} // namespace details
+
+template <typename T>
+void
+serialize(std::ostream &os, T const &t)
+{
+  using namespace std;
+  size_t indent = os.width();
+  os << endl << setw(indent) << "" << details::ltrim(typeid(T).name(), " 0123456789") << ": {" << endl;
+  indent += 2;
+  if constexpr (is_base_of<Extendible<T>, T>::value) {
+    if constexpr (has_super_type<T>::value) {
+      serialize<typename T::super_type>(os, t);
+    }
+    auto const &schema = T::schema;
+    size_t name_width  = 0;
+    for (const auto &kv : schema.fields) {
+      name_width = max(name_width, kv.first.length());
+    }
+    for (const auto &[fname, field] : schema.fields) {
+      ink_assert(field.serializer);
+      os << setw(indent) << "" << setw(name_width) << right << fname << ": ";
+      field.serializer(os, details::FieldPtrGet(t, field));
+      os << "," << endl;
+    }
+  }
+  indent -= 2;
+  os << setw(indent + 1) << "}";
 }
-// private
-template <class Derived_t>
-template <typename Return_t>
-Return_t
-Extendible<Derived_t>::at_offset(ExtendibleOffset_t offset) const
+
+template <typename T>
+std::string
+toString(T const &t)
 {
-  return reinterpret_cast<Return_t>(this_as_char_ptr() + offset);
+  std::stringstream ss;
+  ss.width(0);
+  serialize(ss, t);
+  return ss.str();
 }
+} // namespace ext
 
-// TODO: override std::get<field_t>(Extendible &)
+// C API
+//
 
-}; // namespace MT
+FieldPtr
+ExtFieldPtr(DerivedPtr derived, ExtFieldContext field_context, int *size /*= nullptr*/)
+{
+  using namespace ext;
+  using namespace ext::details;
+  ink_assert(field_context);
+  ink_assert(derived);
+  FieldDesc const &desc = *static_cast<FieldDesc const *>(field_context);
+  if (size) {
+    *size = desc.size;
+  }
+
+  Offest_t const *loc = (Offest_t const *)(uintptr_t(derived) + desc.ext_loc_offset);
+  return FieldPtr(uintptr_t(derived) + (*loc) + desc.field_offset);
+}
diff --git a/src/tscore/Extendible.cc b/src/tscore/Extendible.cc
new file mode 100644
index 0000000..2ca6237
--- /dev/null
+++ b/src/tscore/Extendible.cc
@@ -0,0 +1,139 @@
+/** @file
+
+  Extendible
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  @section details Details
+
+////////////////////////////////////////////
+  Implements:
+  * Extendible<Derived_t>
+  * Schema
+  * fieldAdd
+  * fieldFind
+ */
+
+#include "tscore/Extendible.h"
+
+namespace ext
+{
+namespace details
+{
+  ////////////////////////////////////////////////////
+  // Schema Methods
+
+  Schema::~Schema() {}
+
+  void
+  Schema::updateMemOffsets()
+  {
+    ink_release_assert(instance_count == 0);
+
+    uint32_t acc_offset = 0;
+    alloc_align         = 1;
+
+    for (auto &pair_fld : fields) {
+      alloc_align = std::max(alloc_align, pair_fld.second.align);
+    }
+
+    // allocate fields from largest to smallest alignment
+    uint8_t processing_align = alloc_align;
+    while (processing_align > 0) {
+      uint8_t next_align = 0;
+      for (auto &pair_fld : fields) {
+        auto &fld = pair_fld.second;
+        if (fld.align == processing_align) {
+          fld.field_offset = acc_offset;
+          acc_offset += fld.size;
+        } else if (fld.align < processing_align) {
+          next_align = std::max(next_align, fld.align);
+        }
+      }
+      processing_align = next_align;
+    }
+
+    // align '0' are packed bit allocations.
+    uint32_t acc_bit_offset = 0;
+    for (auto &pair_fld : fields) {
+      auto &fld = pair_fld.second;
+      if (fld.align == 0) {
+        fld.field_offset = acc_offset + acc_bit_offset / 8;
+        fld.mask         = 1 << (acc_bit_offset % 8);
+        ++acc_bit_offset;
+      }
+    }
+
+    alloc_size = acc_offset + (acc_bit_offset + 7) / 8;
+  }
+
+  bool
+  Schema::reset()
+  {
+    if (instance_count > 0) {
+      // free instances before calling this so we don't leak memory
+      return false;
+    }
+    fields.clear();
+    updateMemOffsets();
+    return true;
+  }
+
+  void
+  Schema::callConstructor(uintptr_t ext_loc)
+  {
+    ink_assert(ext_loc);
+    ++instance_count; // don't allow schema modification
+    // init all extendible memory to 0, incase constructors don't
+    memset(reinterpret_cast<void *>(ext_loc), 0, alloc_size);
+
+    for (auto const &elm : fields) {
+      if (elm.second.constructor) {
+        elm.second.constructor(FieldPtr(ext_loc + elm.second.field_offset));
+      }
+    }
+  }
+
+  void
+  Schema::callDestructor(uintptr_t ext_loc)
+  {
+    ink_assert(ext_loc);
+    for (auto const &elm : fields) {
+      if (elm.second.destructor) {
+        elm.second.destructor(FieldPtr(ext_loc + elm.second.field_offset));
+      }
+    }
+    --instance_count;
+  }
+
+  size_t
+  Schema::fullSize(const size_t base_size) const
+  {
+    ink_assert(base_size);
+    return ROUNDUP(base_size, alloc_align) + alloc_size;
+  }
+
+  bool
+  Schema::no_instances() const
+  {
+    return instance_count == 0;
+  }
+} // namespace details
+
+} // namespace ext
diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am
index 714e0bd..868d064 100644
--- a/src/tscore/Makefile.am
+++ b/src/tscore/Makefile.am
@@ -57,6 +57,9 @@ libtscore_la_SOURCES = \
 	CryptoHash.cc \
 	Diags.cc \
 	EventNotify.cc \
+	EventNotify.h \
+	Extendible.cc \
+	Extendible.h \
 	fastlz.c \
 	Hash.cc \
 	HashFNV.cc \
diff --git a/src/tscore/unit_tests/test_AcidPtr.cc b/src/tscore/unit_tests/test_AcidPtr.cc
index 04147be..da5c187 100644
--- a/src/tscore/unit_tests/test_AcidPtr.cc
+++ b/src/tscore/unit_tests/test_AcidPtr.cc
@@ -1,5 +1,5 @@
 /** @file
-  Test file for Extendible
+  Test file for AcidPtr
   @section license License
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
diff --git a/src/tscore/unit_tests/test_Extendible.cc b/src/tscore/unit_tests/test_Extendible.cc
index 3166929..a6b0210 100644
--- a/src/tscore/unit_tests/test_Extendible.cc
+++ b/src/tscore/unit_tests/test_Extendible.cc
@@ -1,6 +1,6 @@
 /** @file
   Test file for Extendible
-  @section license License
+  @INFO license License
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
   distributed with this work for additional information
@@ -18,25 +18,209 @@
 
 #include "catch.hpp"
 
-#include "tscore/Extendible.h"
 #include <iostream>
 #include <string>
-#include <vector>
+#include <array>
 #include <ctime>
 #include <thread>
+
 #include "tscore/ink_atomic.h"
+#include "tscore/Extendible.h"
 
 using namespace std;
-using namespace MT;
+using namespace ext;
+
+//////////////////////////////////////////////////////
+// AtomicBit Tests
+TEST_CASE("AtomicBit Atomic test")
+{
+  // test the atomicity and isolation of operations
+  uint32_t bit_storage = 0;
+
+  auto job_set   = [&bit_storage](int idx) { AtomicBit{(uint8_t *)&bit_storage + (idx / 8), (uint8_t)(1 << (idx % 8))} = 1; };
+  auto job_clear = [&bit_storage](int idx) { AtomicBit{(uint8_t *)&bit_storage + (idx / 8), (uint8_t)(1 << (idx % 8))} = 0; };
+
+  std::thread jobs[32];
+
+  // set all bits in parallel
+  for (int i = 0; i < 32; i++) {
+    jobs[i] = std::thread(job_set, i);
+  }
+  for (int i = 0; i < 32; i++) {
+    jobs[i].join();
+  }
+  REQUIRE(bit_storage == 0xffffffff);
+
+  // clear all bits in parallel
+  for (int i = 0; i < 32; i++) {
+    jobs[i] = std::thread(job_clear, i);
+  }
+  for (int i = 0; i < 32; i++) {
+    jobs[i].join();
+  }
+  REQUIRE(bit_storage == 0);
+}
+
+//////////////////////////////////////////////////////
+// Extendible Inheritance Tests
+
+struct A : public Extendible<A> {
+  uint16_t a = {1};
+};
+
+ext::FieldId<A, std::atomic<uint16_t>> ext_a_1;
+
+class B : public A
+{
+public:
+  using super_type = A;
+  uint16_t b       = {2};
+};
+
+class C : public B, public Extendible<C>
+{
+public:
+  using super_type = B;
+  uint16_t c       = {3};
+
+  // operator[]
+  template <typename F> decltype(auto) operator[](F field) const { return ext::get(*this, field); }
+  template <typename F> decltype(auto) operator[](F field) { return ext::set(*this, field); }
+};
+
+ext::FieldId<C, std::atomic<uint16_t>> ext_c_1;
+
+uintptr_t
+memDelta(void *p, void *q)
+{
+  return uintptr_t(q) - uintptr_t(p);
+}
+TEST_CASE("Extendible Memory Allocations", "")
+{
+  ext::details::areFieldsFinalized() = false;
+  fieldAdd(ext_a_1, "ext_a_1");
+  fieldAdd(ext_c_1, "ext_c_1");
+  ext::details::areFieldsFinalized() = true;
+
+  size_t w = sizeof(uint16_t);
+  CHECK(ext::sizeOf<A>() == w * 3);
+  CHECK(ext::sizeOf<B>() == w * 4);
+  CHECK(ext::sizeOf<C>() == w * 7);
+
+  C &x = *(ext::alloc<C>());
+  //    0   1   2   3   4   5   6
+  //[ EA*,  a,  b,EC*,  c, EA, EC]
+  //
+  uint16_t *mem = (uint16_t *)&x;
+  CHECK(memDelta(&x, &x.a) == w * 1);
+  CHECK(memDelta(&x, &x.b) == w * 2);
+  CHECK(memDelta(&x, &x.c) == w * 4);
+  CHECK(mem[0] == w * (5 - 0));
+  CHECK(mem[1] == 1);
+  CHECK(mem[2] == 2);
+  CHECK(mem[3] == w * (6 - 3));
+  CHECK(mem[4] == 3);
+  CHECK(mem[5] == 0);
+  CHECK(mem[6] == 0);
+
+  std::string format = "\n                            1A | EXT  |     2b |##________##__"
+                       "\n                            1A | BASE |     2b |__##__________"
+                       "\n                            1B | BASE |     2b |____##________"
+                       "\n                            1C | EXT  |     2b |______##____##"
+                       "\n                            1C | BASE |     2b |________##____";
+  CHECK(ext::viewFormat(x) == format);
+
+  printf("\n");
+  delete &x;
+}
+
+TEST_CASE("Extendible Pointer Math", "")
+{
+  C &x = *(ext::alloc<C>());
+
+  CHECK(x.a == 1);
+  CHECK(x.b == 2);
+  CHECK(x.c == 3);
+
+  ext::set(x, ext_a_1) = 4;
+  CHECK(ext::get(x, ext_a_1) == 4);
+  x[ext_c_1] = 5;
+  CHECK(ext::get(x, ext_c_1) == 5);
+
+  CHECK(x.a == 1);
+  CHECK(x.b == 2);
+  CHECK(x.c == 3);
+  CHECK(ext::get(x, ext_a_1) == 4);
+  CHECK(ext::get(x, ext_c_1) == 5);
+
+  std::string format = "\n                            1A | EXT  |     2b |##________##__"
+                       "\n                            1A | BASE |     2b |__##__________"
+                       "\n                            1B | BASE |     2b |____##________"
+                       "\n                            1C | EXT  |     2b |______##____##"
+                       "\n                            1C | BASE |     2b |________##____";
+  CHECK(ext::viewFormat(x) == format);
+
+  ext::FieldId<A, bool> a_bit;
+  ext::FieldId<A, int> a_int;
+  static_assert(std::is_same<decltype(ext::get(x, a_bit)), bool>::value);
+  static_assert(std::is_same<decltype(ext::get(x, a_int)), int const &>::value);
+  delete &x;
+}
 
 // Extendible is abstract and must be derived in a CRTP
 struct Derived : Extendible<Derived> {
   string m_str;
+
+  // operator[] for shorthand
+  template <typename F> decltype(auto) operator[](F field) const { return ext::get(*this, field); }
+  template <typename F> decltype(auto) operator[](F field) { return ext::set(*this, field); }
+
+  static const string
+  testFormat()
+  {
+    const size_t intenal_size = sizeof(Derived) - sizeof(ext::Extendible<Derived>::short_ptr_t);
+    std::stringstream format;
+    format << "\n                      7Derived | EXT  |     1b |##" << string(intenal_size, '_') << "#"
+           << "\n                      7Derived | BASE | " << setw(5) << intenal_size << "b |__" << string(intenal_size, '#')
+           << "_";
+    return format.str();
+  }
 };
 
+///////////////////////////////////////
+// C API for Derived
+//
+void *
+DerivedExtalloc()
+{
+  return ext::alloc<Derived>();
+}
+void
+DerivedExtFree(void *ptr)
+{
+  delete (Derived *)ptr;
+}
+
+ExtFieldContext
+DerivedExtfieldAdd(char const *field_name, int size, void (*construct_fn)(void *), void (*destruct_fn)(void *))
+{
+  // hack to avoid having to repeat this in testing.
+  ext::details::areFieldsFinalized() = false;
+  auto r                             = fieldAdd<Derived>(field_name, size, construct_fn, destruct_fn);
+  ext::details::areFieldsFinalized() = true;
+  return r;
+}
+ExtFieldContext
+DerivedExtfieldFind(char const *field_name)
+{
+  return fieldFind<Derived>(field_name);
+}
+
+///////////////////////////////////////
+
 // something to store more complex than an int
 struct testField {
-  uint8_t arr[5];
+  std::array<uint8_t, 5> arr;
   static int alive;
   testField()
   {
@@ -57,18 +241,31 @@ struct testField {
 };
 int testField::alive = 0;
 
+namespace ext
+{
+template <>
+void
+serializeField(ostream &os, testField const &t)
+{
+  serializeField(os, t.arr);
+}
+} // namespace ext
+
 TEST_CASE("Extendible", "")
 {
-  typename Derived::BitFieldId bit_a, bit_b, bit_c;
-  typename Derived::FieldId<ATOMIC, int> int_a, int_b, int_c;
+  printf("\nsizeof(string) = %lu", sizeof(std::string));
+  printf("\nsizeof(Derived) = %lu", sizeof(Derived));
+
+  ext::FieldId<Derived, bool> bit_a, bit_b, bit_c;
+  ext::FieldId<Derived, std::atomic<int>> int_a, int_b, int_c;
   Derived *ptr;
 
   // test cases:
   //[constructor] [operator] [type] [access] [capacity] [modifier] [operation] [compare] [find]
-  // I don't use INFOS because this modifies static variables many times, is not thread safe.
+  // I don't use SECTIONS because this modifies static variables many times, is not thread safe.
   INFO("Extendible()")
   {
-    ptr = new Derived();
+    ptr = ext::alloc<Derived>();
     REQUIRE(ptr != nullptr);
   }
 
@@ -80,223 +277,174 @@ TEST_CASE("Extendible", "")
 
   INFO("Schema Reset")
   {
-    ptr = new Derived();
+    ptr = ext::alloc<Derived>();
+    REQUIRE(Derived::schema.no_instances() == false);
     REQUIRE(Derived::schema.reset() == false);
     delete ptr;
+    REQUIRE(Derived::schema.no_instances() == true);
+    ext::details::areFieldsFinalized() = false;
     REQUIRE(Derived::schema.reset() == true);
+    ext::details::areFieldsFinalized() = true;
   }
 
   INFO("shared_ptr")
   {
-    shared_ptr<Derived> sptr(new Derived());
+    shared_ptr<Derived> sptr(ext::alloc<Derived>());
+    REQUIRE(Derived::schema.no_instances() == false);
     REQUIRE(sptr);
   }
+  REQUIRE(Derived::schema.no_instances() == true);
 
   INFO("add a bit field")
   {
     //
-    REQUIRE(Derived::schema.addField(bit_a, "bit_a"));
+    ext::details::areFieldsFinalized() = false;
+    REQUIRE(fieldAdd(bit_a, "bit_a"));
+    ext::details::areFieldsFinalized() = true;
   }
 
-  INFO("test bit field")
+  INFO("Extendible delete ptr");
   {
-    shared_ptr<Derived> sptr(new Derived());
-    auto &ref = *sptr;
-    ref.writeBit(bit_a, 1);
-    CHECK(ref[bit_a] == 1);
+    for (int i = 0; i < 10; i++) {
+      ptr = ext::alloc<Derived>();
+      REQUIRE(ptr != nullptr);
+      INFO(__LINE__);
+      REQUIRE(Derived::schema.no_instances() == false);
+      delete ptr;
+      INFO(__LINE__);
+      ptr = nullptr;
+      REQUIRE(Derived::schema.no_instances() == true);
+    }
   }
 
-  INFO("test bit packing")
+  INFO("test bit field");
   {
-    REQUIRE(Derived::schema.reset() == true);
-    CHECK(Derived::schema.size() == sizeof(std::string));
+    shared_ptr<Derived> sptr{ext::alloc<Derived>()};
+    Derived &ref = *sptr;
+
+    CHECK(ext::viewFormat(ref) == Derived::testFormat());
+
+    AtomicBit bitref = ext::set(ref, bit_a);
+    bitref           = 1;
+    CHECK(bitref == true);
+    bitref = true;
+    CHECK(bitref == true);
+    CHECK(ext::set(ref, bit_a) == true);
+    CHECK(ext::get(ref, bit_a) == true);
+  }
 
-    REQUIRE(Derived::schema.addField(bit_a, "bit_a"));
-    CHECK(Derived::schema.size() == sizeof(std::string) + 1);
-    REQUIRE(Derived::schema.addField(bit_b, "bit_b"));
-    CHECK(Derived::schema.size() == sizeof(std::string) + 1);
-    REQUIRE(Derived::schema.addField(bit_c, "bit_c"));
-    CHECK(Derived::schema.size() == sizeof(std::string) + 1);
+  INFO("test bit packing")
+  {
+    struct size_test {
+      string s;
+      uint16_t i;
+    };
 
-    shared_ptr<Derived> sptr(new Derived());
+    REQUIRE(Derived::schema.reset() == true);
+    CHECK(sizeof(Extendible<Derived>) == sizeof(uint16_t));
+    CHECK(sizeof(size_test) == ROUNDUP(sizeof(std::string) + sizeof(uint16_t), alignof(std::string)));
+    CHECK(sizeof(Derived) == sizeof(size_test));
+    CHECK(ext::sizeOf<Derived>() == sizeof(Derived));
+
+    ext::details::areFieldsFinalized() = false;
+    REQUIRE(fieldAdd(bit_a, "bit_a"));
+    size_t expected_size = sizeof(Derived) + 1;
+    CHECK(ext::sizeOf<Derived>() == expected_size);
+    REQUIRE(fieldAdd(bit_b, "bit_b"));
+    CHECK(ext::sizeOf<Derived>() == expected_size);
+    REQUIRE(fieldAdd(bit_c, "bit_c"));
+    CHECK(ext::sizeOf<Derived>() == expected_size);
+    ext::details::areFieldsFinalized() = true;
+
+    shared_ptr<Derived> sptr(ext::alloc<Derived>());
     Derived &ref = *sptr;
-    ref.writeBit(bit_a, true);
-    ref.writeBit(bit_b, false);
-    ref.writeBit(bit_c, true);
-    CHECK(ref[bit_a] == true);
-    CHECK(ref[bit_b] == false);
-    CHECK(ref[bit_c] == true);
+    CHECK(ext::viewFormat(ref) == Derived::testFormat());
+    using Catch::Matchers::Contains;
+    REQUIRE_THAT(ext::toString(ref), Contains("bit_a: 0"));
+    REQUIRE_THAT(ext::toString(ref), Contains("bit_b: 0"));
+    REQUIRE_THAT(ext::toString(ref), Contains("bit_c: 0"));
+
+    ext::set(ref, bit_a) = 1;
+    ext::set(ref, bit_b) = 0;
+    ext::set(ref, bit_c) = 1;
+    CHECK(ext::get(ref, bit_a) == true);
+    CHECK(ext::get(ref, bit_b) == false);
+    CHECK(ext::get(ref, bit_c) == true);
+    REQUIRE_THAT(ext::toString(ref), Contains("bit_a: 1"));
+    REQUIRE_THAT(ext::toString(ref), Contains("bit_b: 0"));
+    REQUIRE_THAT(ext::toString(ref), Contains("bit_c: 1"));
   }
 
   INFO("store int field")
   {
-    REQUIRE(Derived::schema.addField(int_a, "int_a"));
-    REQUIRE(Derived::schema.addField(int_b, "int_b"));
-    REQUIRE(Derived::schema.size() == sizeof(std::string) + 1 + sizeof(std::atomic_int) * 2);
+    ext::details::areFieldsFinalized() = false;
+    REQUIRE(fieldAdd(int_a, "int_a"));
+    REQUIRE(fieldAdd(int_b, "int_b"));
+    ext::details::areFieldsFinalized() = true;
+
+    size_t expected_size = sizeof(Derived) + 1 + sizeof(std::atomic_int) * 2;
+    CHECK(ext::sizeOf<Derived>() == expected_size);
 
-    shared_ptr<Derived> sptr(new Derived());
+    shared_ptr<Derived> sptr(ext::alloc<Derived>());
     Derived &ref = *sptr;
-    CHECK(ref.get(int_a) == 0);
-    CHECK(ref.get(int_b) == 0);
-    ++ref.get(int_a);
-    ref.get(int_b) = 42;
-    ref.m_str      = "Hello";
-    CHECK(ref.get(int_a) == 1);
-    CHECK(ref.get(int_b) == 42);
+    CHECK(ext::get(ref, int_a) == 0);
+    CHECK(ext::get(ref, int_b) == 0);
+    ++ext::set(ref, int_a);
+    ext::set(ref, int_b) = 42;
+    ref.m_str            = "Hello";
+    CHECK(ext::get(ref, int_a) == 1);
+    CHECK(ext::get(ref, int_b) == 42);
     CHECK(ref.m_str == "Hello");
   }
 
-  INFO("C API add int field")
-  {
-    FieldId_C cf_a = Derived::schema.addField_C("cf_a", 4, nullptr, nullptr);
-    CHECK(Derived::schema.size() == sizeof(std::string) + 1 + sizeof(std::atomic_int) * 2 + 4);
-    CHECK(Derived::schema.find_C("cf_a") == cf_a);
-  }
+  INFO("Extendible Test Complete")
+}
 
-  INFO("C API alloc instance")
-  {
-    shared_ptr<Derived> sptr(new Derived());
-    CHECK(sptr.get() != nullptr);
-  }
+TEST_CASE("Extendible C API")
+{
+  ext::details::areFieldsFinalized() = false;
+  Derived::schema.reset();
+  CHECK(Derived::schema.no_instances() == true);
+  ext::details::areFieldsFinalized() = true;
 
-  INFO("C API test int field")
+  INFO("C API alloc instance")
   {
-    shared_ptr<Derived> sptr(new Derived());
-    Derived &ref   = *sptr;
-    FieldId_C cf_a = Derived::schema.find_C("cf_a");
-    uint8_t *data8 = (uint8_t *)ref.get(cf_a);
-    CHECK(data8[0] == 0);
-    ink_atomic_increment(data8, 1);
-    *(data8 + 1) = 5;
-    *(data8 + 2) = 7;
+    void *d = DerivedExtalloc();
 
-    ref.m_str = "Hello";
+    CHECK(d != nullptr);
+    CHECK(Derived::schema.no_instances() == false);
 
-    uint32_t *data32 = (uint32_t *)ref.get(cf_a);
-    CHECK(*data32 == 0x00070501);
-    CHECK(ref.m_str == "Hello");
-  }
+    DerivedExtFree(d);
 
-  Derived::FieldId<ACIDPTR, testField> tf_a;
-  INFO("ACIDPTR add field")
-  {
-    REQUIRE(Derived::schema.addField(tf_a, "tf_a"));
-    CHECK(Derived::schema.size() == sizeof(std::string) + 1 + sizeof(std::atomic_int) * 2 + 4 + sizeof(std::shared_ptr<testField>));
-    REQUIRE(Derived::schema.find<ACIDPTR, testField>("tf_a").isValid());
+    CHECK(Derived::schema.no_instances() == true);
   }
 
-  INFO("ACIDPTR test")
+  INFO("C API add int field")
   {
-    shared_ptr<Derived> sptr(new Derived());
-    Derived &ref = *sptr;
-    // ref.m_str    = "Hello";
-    auto tf_a = Derived::schema.find<ACIDPTR, testField>("tf_a");
-    {
-      std::shared_ptr<const testField> tf_a_sptr = ref.get(tf_a);
-      const testField &dv                        = *tf_a_sptr;
-      CHECK(dv.arr[0] == 1);
-      CHECK(dv.arr[1] == 2);
-      CHECK(dv.arr[2] == 4);
-      CHECK(dv.arr[3] == 8);
-      CHECK(dv.arr[4] == 16);
-    }
-    CHECK(testField::alive == 1);
-  }
+    ExtFieldContext cf_a = DerivedExtfieldAdd("cf_a", 4, nullptr, nullptr);
 
-  INFO("ACIDPTR destroyed")
-  {
-    //
-    CHECK(testField::alive == 0);
-  }
-
-  INFO("AcidPtr AcidCommitPtr malloc ptr int");
-  {
-    void *mem            = malloc(sizeof(AcidPtr<int>));
-    AcidPtr<int> &reader = *(new (mem) AcidPtr<int>);
-    {
-      auto writer = reader.startCommit();
-      CHECK(*writer == 0);
-      *writer = 1;
-      CHECK(*writer == 1);
-      CHECK(*reader.getPtr().get() == 0);
-      // end of scope writer, commit to reader
-    }
-    CHECK(*reader.getPtr().get() == 1);
-    reader.~AcidPtr<int>();
-    free(mem);
-  }
-  INFO("AcidPtr AcidCommitPtr casting");
-  {
-    void *mem                  = malloc(sizeof(AcidPtr<testField>));
-    AcidPtr<testField> &reader = *(new (mem) AcidPtr<testField>);
-    {
-      auto writer = reader.startCommit();
-      CHECK(writer->arr[0] == 1);
-      CHECK(reader.getPtr()->arr[0] == 1);
-      writer->arr[0] = 99;
-      CHECK(writer->arr[0] == 99);
-      CHECK(reader.getPtr()->arr[0] == 1);
-    }
-    CHECK(reader.getPtr()->arr[0] == 99);
-    reader.~AcidPtr<testField>();
-    free(mem);
+    size_t expected_size = sizeof(Derived) + 4;
+    CHECK(ext::sizeOf<Derived>() == expected_size);
+    CHECK(DerivedExtfieldFind("cf_a") == cf_a);
   }
-  INFO("ACIDPTR block-free reader")
+  INFO("C API test int field")
   {
-    auto tf_a = Derived::schema.find<ACIDPTR, testField>("tf_a");
-    REQUIRE(tf_a.isValid());
-    Derived &d = *(new Derived());
-    CHECK(d.get(tf_a)->arr[0] == 1);
-    { // write 0
-      AcidCommitPtr<testField> w = d.writeAcidPtr(tf_a);
-      REQUIRE(w != nullptr);
-      REQUIRE(w.get() != nullptr);
-      w->arr[0] = 0;
-    }
-    // read 0
-    CHECK(d.get(tf_a)->arr[0] == 0);
-    // write 1 and read 0
-    {
-      AcidCommitPtr<testField> tf_a_wtr = d.writeAcidPtr(tf_a);
-      tf_a_wtr->arr[0]                  = 1;
-      CHECK(d.get(tf_a)->arr[0] == 0);
-      // [end of scope] write is committed
-    }
-    // read 1
-    CHECK(d.get(tf_a)->arr[0] == 1);
-    delete &d;
-  }
+    void *d = DerivedExtalloc();
+    REQUIRE(d != nullptr);
 
-  INFO("STATIC")
-  {
-    typename Derived::FieldId<STATIC, int> tf_d;
-    Derived::schema.addField(tf_d, "tf_d");
-    REQUIRE(tf_d.isValid());
-    Derived &d         = *(new Derived());
-    d.init(tf_d)       = 5;
-    areStaticsFrozen() = true;
-
-    CHECK(d.get(tf_d) == 5);
-
-    // this asserts when areStaticsFrozen() = true
-    // CHECK(d.init(tf_d) == 5);
-    delete &d;
-  }
+    ExtFieldContext cf_a = DerivedExtfieldFind("cf_a");
+    uint8_t *data8       = (uint8_t *)ExtFieldPtr(d, cf_a);
 
-  INFO("DIRECT")
-  {
-    typename Derived::FieldId<DIRECT, int> tf_e;
-    Derived::schema.addField(tf_e, "tf_e");
-    REQUIRE(tf_e.isValid());
-    Derived &d  = *(new Derived());
-    d.get(tf_e) = 5;
-
-    CHECK(d.get(tf_e) == 5);
+    CHECK(data8[0] == 0);
+    ink_atomic_increment(&data8[0], 1);
+    data8[1] = 5;
+    data8[2] = 7;
 
-    // this asserts when areStaticsFrozen() = true
-    // CHECK(d.init(tf_e) == 5);
-    delete &d;
+    uint32_t *data32 = (uint32_t *)ExtFieldPtr(d, cf_a);
+    CHECK(*data32 == 0x00070501);
+    DerivedExtFree(d);
   }
-
-  INFO("Extendible Test Complete")
+  INFO("Extendible C API Test Complete")
 }
+
+//*/


Mime
View raw message