xo-umbrella2/xo-alloc2
2026-02-01 22:12:28 -05:00
..
cmake xo-arena xo-facet xo-alloc2: build restored 2026-01-06 00:01:38 -05:00
docs xo-gc xo-object2 xo-alloc2: templates for FOMO [WIP] 2025-12-24 19:39:11 -05:00
include/xo/alloc2 xo-interpreter2: work towards utest w/ vsm+reader [WIP] 2026-02-01 22:12:28 -05:00
src/alloc2 xo-arena: annex DArena.* DArenaIterator.* from xo-alloc2 2026-01-06 00:49:41 -05:00
utest xo-alloc2: prefer with_facet in arena utest 2026-01-13 16:43:14 -05:00
CMakeLists.txt + xo-printable2 + build fixes for cmake config 2026-01-04 23:03:18 -05:00
README.md xo-alloc2: scaffold for interface+data separation 2025-12-07 15:55:33 -05:00

xo-alloc2 -- arena allocator and incremental garbage collector

Relative to xo-alloc:

  1. keep interface and data separate. 1a. Representation or Data classes. Entirely passive; strictly no methods. motivation: data doesn't carry any linker-dependency baggage; it's just layout.

    example:
    
         struct DPolar { double arg; double mag; };
         struct DRRect { double x; double y; };

1b. Interface classes. These have abstract methods only. motivation: for runtime polymorphism, specify interface without assuming anything about data layout. Methods in an interface will take opaque data pointer as first argument.

   example:
         struct AComplex {
            using repr_type = void;

            virtual double xcoord(void * repr) const = 0;
            virtual double ycoord(void * repr) const = 0;
            virtual double magnitude(void * repr) const = 0;
            virtual double argument(void * repr) const = 0;
         };

1c. Implementation classes. Implement a specific interface (as in 1b) for a specific data representation (as in 1a). All methods will be final override. Methods in implementation, since they inherit an interface, wil have opaque data pointer as their first argument. They will downcast this pointer to specific target representation.

   example:
         struct IComplex_Rect : public AComplex {
           using repr_type = RRect;

           double _xcoord(RRect * repr) const { return repr->x; }
           double _ycoord(RRect * repr) const { return repr->y; }
           double _magnitude(RRect * repr) const {
             double x = repr->x;
             double y = repr->y;
             return ::sqrt(x*x + y*y);
           }
           double _argument(RRect * repr) const {
             double tan = repr->x / repr->y;
             return ::arctan(tan);
           }

           // implement IComplex for RRect
           double xcoord(void * repr) const final override {
             return _xcoord((RRect*)repr);
           }
           double ycoord(void * repr) const final override;
           double magnitude(void * repr) const final override;
           double argument(void * repr) const final override;
         };

         struct IComplex_Polar : public AComplex {
           using repr_type = RPolar;

           // implement IComplex for RPolar
           ..similar..
         };
   Here `IComplex_Rect` and `IComplex_Polar` are constructible.
   They're concrete in the sense that they expect a specific representation
   (`IComplex_Rect::repr_type`, `IComplex_Polar::repr_type` respectively).

1d. Object classes. Pair implementation and interface. May use smart pointer here to express strategy for managing memory used for representation. Don't expect to need this for interfaces, since interface content entirely known at compile time.

   example:
         // borrowed
         struct OComplex_Rect : public IComplex_Rect {
           DRect * data() const { return data_; }

           bp<DRect> data_;   // naked pointer
         };

         struct OComplex_Polar : public IComplex_Polar {
           DPolar * data() const { return data_; }

           bp<DPolar> data_;
         };

         // unique
         struct OComplex_Rect : public IComplex_Rect {
           DRect * data() const { return data_; }

           up<DRect> data_;  // unique_ptr
         };

         ..
    Can do this generically.
          // in bx: 'b' short for 'borrowed' as in unowned.
          //        'x' just to distinguish from 'pointer'.
          //
          template <typename Iface,
                    typename Repr = typename Iface::repr_type>
          struct bx : public Iface {
            explicit bx(Repr * data) : data_{data} {}
            Repr * data() const { return data_; }

            bp<Repr> data_;
          };

          DRect z1_data{1.0, -1.0};
          bx<IComplex_Rect> z1{&z1_data};

          DPolar z2_data{sqrt(2.0), pi * 8/7};
          bx<IComplex_Polar> z2{&z2_data};
    Then to invoke a method (compile-time polymorphism)
          z1._xcoord(z1.data());

1e. Runtime polymorphism Observe that bxp<Complex_Rect> and bxp<Complex_Polar> have the same top-level representation. - Both have iface member that inherits IComplex, - both have data pointer compatible with their respective iface member Can have common representation for runtime polymorphism - bxp<Complex_Rect> and bxp<Complex_Polar> have the same size and compatible representation. - both inherit IComplex - safe to reinterpret cast

         // type-erased (placeholder, never used)
         struct IComplex_Any : public AComplex {
           using repr_type = void;

           double xcoord(void * repr) const final override { assert(false); return 0.0; }
         };

         bx<IComplex_Rect> z1 = ...;
         bx<IComplex_Any> z1_any = reinterpret_cast<IComplex_Any>(z1);
   Capturing the pattern:
        // in abstract interface
        struct AComplex {
          using ErasedIfaceType = IComplex_Any;
          ..
        }

        template<typename Iface,
                 typename Repr = typename Iface::repr_type>
        struct bx : public Iface {
          ..
          operator bx<Iface::typename ErasedIfaceType>() {
            // in particular, overwrites vtable pointer
            return reinterpret_cast<bx<Iface::typename ErasedIfaceType>>(*this);
          }
          ..
        };
        ```

   2. Remarks
      - shared pattern with pimpl idiom,
        except impl isn't private
      - can use the same Data type with an unrelated interface.
        Although lose the automatic assocation
      - can put forwarding methods into object structs,
        though will be boilerplatey.

          struct bxp_ext : public bxp {
            double xcoord() { return iface->xcoord(data); }
            double ycoord() { return iface->ycoord(date); }
            double magnitude() { return iface->magnitude(data); }
            double argument() { return iface->argument(data); }
          };
      - since interface and data are segregated,
        it's easier to devirtualize. Interface pointers are explicit,
        and don't need to be changed to refer to different data.