Skip to content

Commit

Permalink
vptr -> dynamic_vptr
Browse files Browse the repository at this point in the history
  • Loading branch information
jll63 committed Feb 3, 2024
1 parent ecc9b33 commit 6eae572
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 138 deletions.
2 changes: 1 addition & 1 deletion include/yorel/yomm2/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ method<Policy, Key, R(A...)>::vptr(const ArgType& arg) const {
// No need to check the method pointer: this was done when the
// virtual_ptr was created.
} else {
return Policy::vptr(arg);
return Policy::dynamic_vptr(arg);
}
}

Expand Down
16 changes: 0 additions & 16 deletions include/yorel/yomm2/detail.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,22 +162,6 @@ template<typename Container>
constexpr bool has_trace =
!std::is_same_v<decltype(type_trace(std::declval<Container>())), void>;

// --------------
// intrusive mode

void type_mptr(...);

template<typename Object>
auto type_mptr(Object* obj) -> decltype(obj->yomm2_vptr());

template<typename Object>
using type_mptr_t = decltype(type_mptr(std::declval<Object*>()));

template<typename Object>
constexpr bool has_mptr = !std::is_same_v<
decltype(type_mptr(std::declval<std::remove_reference_t<Object>*>())),
void>;

template<typename T>
const char* default_method_name() {
#if defined(__GXX_RTTI) || defined(_HAS_STATIC_RTTI)
Expand Down
4 changes: 2 additions & 2 deletions include/yorel/yomm2/policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ struct yOMM2_API_gcc external_vptr_vector : virtual external_vptr {
}

template<class Class>
static auto vptr(const Class& arg) {
static auto dynamic_vptr(const Class& arg) {
auto index = Policy::dynamic_type(arg);

if constexpr (has_facet<Policy, type_hash>) {
Expand Down Expand Up @@ -365,7 +365,7 @@ struct yOMM2_API_gcc external_vptr_map : virtual external_vptr {
}

template<class Class>
static auto vptr(const Class& arg) {
static auto dynamic_vptr(const Class& arg) {
return vptrs.find(Policy::dynamic_type(arg))->second;
}
};
Expand Down
154 changes: 96 additions & 58 deletions reference.in/vptr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,46 @@ its static functions to allow it to initialize its data structures.
## Example
This example involves two hierarchies, `Person` (with one subclass, `Engineer`),
and `Number` (with two subclasses, `Integer` and `Rational`). Each hierarchy
collaborates with YOMM2 by providing the pointer to the vtable. The classes are
_not_ polymorphic.
Virtual functions are sometimes criticized for having a non-zero overhead.
Consider the following class hierarchy:
In the `Person` hierarchy, the vptr is stored directly in the object[^1]. The
constructor initializes it with the appropriate vtable, obtained from
`basic_policy::vptr`.
***/

In the `Number` hierarchy, objects are stored in pages, aligned on a 1024 byte
boundary. A page contains objects of the same type, either `Integer` or
`Rational`. The appropriate vptr is stored at the beginning of the page, which
can be found by applying a mask (`~1023`) to the `this` pointer.
//***
struct Number {};

***/
struct Integer : Number {
explicit Integer(int value) : value(value) {
}

int value;
};

struct Rational : Number {
Rational(int a, int b) : num(a), den(b) {
}

int num, den;
};
//***

/*** Making these classes polymorphic would double the size of `Integer`, and
increase the size of `Rational` by 50%.
A possible solution is to allocate objects in homogeneous pages, and store the
pointer to the v-table at the beginning of each page. It is thus shared between
a large number of objects. To make it easy to find the beginning of the page, we
allocate the pages on a 1024 byte boundary.
For this, we create a new `vptr` facet, which checks if the object is derived
from `Number`. If yes, it locates the base of the page (`&obj & ~1023`) and
returns the vptr stored there. Otherwise, it falls back to the default behavior,
so we can have both `Number` and normal, polymorphic classes as parameters in
multi-methods.
***/

//***
#ifdef _MSC_VER
#include <malloc.h>
#else
Expand All @@ -76,55 +98,30 @@ can be found by applying a mask (`~1023`) to the `this` pointer.
using namespace yorel::yomm2;
using namespace policy;

struct Person;
struct Number;

struct vptr_page : virtual default_static_policy::use_facet<vptr> {
template<class QClass>
static auto vptr(QClass& arg) {
using Class = std::remove_const_t<QClass>;
if constexpr (std::is_base_of_v<Number, Class>) {
template<class Class>
static auto dynamic_vptr(Class& arg) {
if constexpr (std::is_base_of_v<Number, std::remove_const_t<Class>>) {
auto page = reinterpret_cast<std::uintptr_t>(&arg) & ~1023;
return *reinterpret_cast<std::uintptr_t**>(page);
} else if constexpr (std::is_base_of_v<Person, Class>) {
return arg.vptr;
} else {
return default_static_policy::use_facet<policy::vptr>::vptr(arg);
return default_static_policy::use_facet<vptr>::dynamic_vptr(arg);
}
}
};

struct custom_policy : default_static_policy::replace<vptr, vptr_page> {};

struct Number {};

struct Integer : Number {
explicit Integer(int value) : value(value) {
}
struct number_aware_policy : default_static_policy::replace<vptr, vptr_page> {};

int value;
};

struct Rational : Number {
Rational(int a, int b) : num(a), den(b) {
}

int num, den;
};
// Make it the default policy.
#define YOMM2_DEFAULT_POLICY number_aware_policy
#include <yorel/yomm2/keywords.hpp>
#include <yorel/yomm2/runtime.hpp>

struct Person {
std::uintptr_t* vptr;
//***

Person() {
vptr = custom_policy::static_vptr<Person>;
}
};
/*** Here is a bare-bone implementation of the page allocator: ***/

struct Engineer : Person {
Engineer() {
vptr = custom_policy::static_vptr<Engineer>;
}
};
//***

template<class T>
class Page {
Expand All @@ -143,7 +140,7 @@ class Page {
#endif
);
*reinterpret_cast<std::uintptr_t**>(base) =
custom_policy::static_vptr<T>;
number_aware_policy::static_vptr<T>;
void* first = base + sizeof(std::uintptr_t*);
size_t space = page_size - sizeof(std::uintptr_t*);
std::align(alignof(T), sizeof(T), first, space);
Expand All @@ -167,14 +164,27 @@ class Page {
return *new (top++) T(std::forward<U>(args)...);
}
};
//***

#define YOMM2_DEFAULT_POLICY custom_policy
#include <yorel/yomm2/keywords.hpp>
#include <yorel/yomm2/runtime.hpp>
/***
Let's create a couple of "ordinary" classes, register all the classes, and
define a method:
***/

//***

struct Person {
virtual ~Person() {
}
};

struct Engineer : Person {};

register_classes(Number, Integer, Rational, Person, Engineer);

#include <iostream>
#include <iosfwd>

declare_method(
void, add,
Expand All @@ -193,32 +203,57 @@ define_method(
os << a.value << " + " << b.value << " = " << a.value + b.value;
}

define_method(
void, add,
(const Engineer&, const Integer& a, const Rational& b, std::ostream& os)) {
os << a.value << " + " << b.num << "/" << b.den << " = "
<< a.value * b.den + b.num << "/" << b.den;
}

define_method(
void, add,
(const Engineer&, const Rational& a, const Integer& b, std::ostream& os)) {
os << a.num << "/" << a.den << " + " << b.value << " = "
<< a.num + b.value * a.den << "/" << a.den;
}

define_method(
void, add,
(const Engineer&, const Rational& a, const Rational& b, std::ostream& os)) {
os << a.num << "/" << a.den << " + " << b.num << "/" << b.den << " = "
<< a.num * b.den + a.den * b.num << "/" << a.den * b.den;
}

//***

/***
And now let's test:
***/

//***

#include <sstream>

BOOST_AUTO_TEST_CASE(ref_vptr_page) {
// Check for zero overhead.
static_assert(sizeof(Integer) == sizeof(int));
static_assert(sizeof(Rational) == 2 * sizeof(int));

update<custom_policy>();
// Call update(). This must be done before we create any Numbers.
update<number_aware_policy>();

// Allocate a few Integers...
Page<Integer> ints;
const Number &i_2 = ints.construct(2), &i_3 = ints.construct(3);

// ...and a few Rationals.
Page<Rational> rationals;
const Number &r_2_3 = rationals.construct(2, 3),
&r_3_4 = rationals.construct(3, 4);
&r_3_4 = rationals.construct(3, 4);

const Person& alice = Engineer();
const Person& bob = Person();

std::ostringstream out1, out2, out3;
std::ostringstream out1, out2, out3, out4; // to capture results

add(bob, i_2, i_3, out1);
BOOST_TEST(out1.str() == "2 + 3 = 5");
Expand All @@ -228,6 +263,9 @@ BOOST_AUTO_TEST_CASE(ref_vptr_page) {

add(alice, r_2_3, r_3_4, out3);
BOOST_TEST(out3.str() == "2/3 + 3/4 = 17/12");

add(alice, i_2, r_2_3, out4);
BOOST_TEST(out4.str() == "2 + 2/3 = 8/3");
}

//***
Expand Down
Loading

0 comments on commit 6eae572

Please sign in to comment.