When Felix compiler see a heap allocation for an object which is a product A x B,
it uses ScanByOffsets technology to construct an array of offsets into the whole
objects of all the pointers, by concatenating the offset array for A with the
offset array for B with the size of A added to each of B’s offsets.
This is done recursively into in the static nesting structure of products to produce
a single linear array of offsets.
Now, for a user defined primitive lifted by C such as a C++ list of T,
we can write a scanner for C++ list which also knows where the points
in a fixed T are.
The first problem here is that there’s no way to *dynamically* pass in the RTTI
of T. The interface of a scanner is:
typedef void *scanner_t(collector_t*, gc_shape_t *, void *, size_t, int);
The void* there is the object to be scanned, with type given by gc_shape_t.
struct GC_EXTERN gc_shape_t
{
char const *cname; ///< C++ typename
::std::size_t count; ///< static array element count
::std::size_t amt; ///< bytes allocated
finaliser_t *finaliser; ///< finalisation function
ValueType *fcops; ///< first class ops
void const *private_data; ///< private data passed to scanner
scanner_t *scanner; ///< scanner function
encoder_t *encoder; ///< encoder function
decoder_t *decoder; ///< encoder function
gc_shape_flags_t flags; ///< flags
size_t allocations;
size_t deallocations;
};
There IS provision there for passing extra data to the scanner: the scanner is included
in the shape object, at it is passed the shape object as an argument, so it can get the
private_data.
So the scanner for a C++ List<T> where T is a Felix struct containing pointers,
can be done by putting the shape of that struct in the private data. The same
scanner then works for all C++ lists (the scanner, trivially, is just a loop from
begin() to end() with the iterator locating the data).
But there is no way for the user to set the private_data, and the user cannot trivially
construct as shape object. The ability to specify the data type for a container
is required. Note that the scanner is then a FUNCTOR in that it works
for all data types, that is, it is dynamically polymorphic. The problem is largely
a matter of syntax for specifying the private data. Note that some data structures
might contain more than one data type so just passing a single shape is not enough,
but an array of shape pointers is enough.
This leads to a second problem. At present in Felix, the compiler CANNOT handle
objects CONTAINING a primitive type with a user specified scanner. There
are two cases only supported: the primitive is atomic and is not examined,
or, the primitive is a pointer to an object in the Felix heap.
Pointers to C objects are ignored. If the pointer is to a C++ allocated List
containing Felix objects we’re screwed. We need to be able to scan such
containers even if Felix didn’t allocate them. There’s no “scannable but
not allocated by us” category.
But the main point is that to include such “foreign” objects in Felix data
structure you HAVE to use a pointer, and the Felix representation is then
marked “gc_pointer” so the compiler knows it is a pointer. And you HAVE
to allocate the object with Felix GC otherwise, the pointer will be thrown
out and not scanned. The point is you cannot put the expanded object
into the Felix object, only a pointer.
To FIX this, a new RTTI form is required. I’ve done a lot of work trying
to get dynamic information on types available. The “fcops” pointer
in the shape object points to this:
class ValueType
{
virtual size_t object_size_impl()=0;
virtual size_t object_alignment_impl()=0;
virtual void dflt_init_impl (void *)=0;
virtual void destroy_impl (void *)=0;
virtual void copy_init_impl(void *, void *)=0;
virtual void move_init_impl(void *, void *)=0;
virtual void copy_assign_impl(void *, void *)=0;
virtual void move_assign_impl(void *, void *)=0;
public:
size_t object_size() { return object_size_impl(); }
size_t object_alignment() { return object_size_impl(); }
void dflt_init(void *dst) { dflt_init_impl(dst); }
void destroy(void *dst) { destroy_impl (dst); }
void copy_init (void *dst, void *src) { copy_init_impl(dst,src); }
void move_init (void *dst, void *src) { move_init_impl(dst,src); }
void copy_assign(void *dst, void *src) { copy_assign_impl(dst,src); }
void move_assign(void *dst, void *src) { move_assign_impl(dst,src); }
};
For C++ objects here’s an implementation:
template<typename T>
class CxxValueType : public virtual ValueType
{
size_t object_size_impl() { return sizeof(T); }
size_t object_alignment_impl() { return alignof(T); }
void dflt_init_impl(void *dst) { ::dflt_init<T>((T*)dst); }
void destroy_impl(void *dst) { ::dflt_init<T>((T*)dst); }
void copy_init_impl(void *dst, void *src) { ::copy_init<T>((T*)dst,(T*)src); }
void move_init_impl(void *dst, void *src) { ::move_init<T>((T*)dst,(T*)src); }
void copy_assign_impl(void *dst, void *src) { ::copy_assign<T>((T*)dst,(T*)src); }
void move_assign_impl(void *dst, void *src) { ::move_assign<T>((T*)dst,(T*)src); }
};
Now here’s the point:
// object does NOT own the product description array
// should use a shared pointer thing I guess
class ProductType : public virtual ValueType
{
size_t n;
ValueType **cp;
public:
ProductType (ValueType **p, size_t m) : cp(p), n(n) {}
~ProductType();
size_t object_size_impl() override;
size_t object_alignment_impl() override;
void dflt_init_impl (void *) override;
void destroy_impl (void *) override;
void copy_init_impl(void *, void *) override;
void move_init_impl(void *, void *) override;
void copy_assign_impl(void *, void *) override;
void move_assign_impl(void *, void *) override;
};
This ValueType allows a product to be constructed dynamically.
This is dynamic typing! The constructed object can be faithfully
copied etc, the same way C++ does it: elementwise.
NOTE: these products are expanded! That is, they’re actually descriptions
of a single contiguous object, there’s no “boxing” involved. The point of
this work is that FPL’s are WRONG to use boxing, its just a cheat.
One can still have code calling a function with more than one
data type, and still copy the data, even if the data type is not
known until run time, so in particular its size isn’t known.
We just use RTTI to allocate and copy the object.
Now here’s the POINT: the GC needs to be able to handle products too.
In particular you might have A x B, with A using ScanByOffsets like a normal
Felix objects, and B being a C++ list. Not a pointer to a C++ list, an actual list.
Using a *product* form of the RTTI similar to the ValueType, we can scan
the object using TWO scanners, first the one for A, and then the one for B,
passing B the address of the object plus the size of A.
In particular the “stuff” in the shape object really belongs in the ValueType
object, and the shape object can be eliminated.
Note that we still want the compiler to GENERATE a composite RTTI object
NOT use a product if possible because it’s faster.
—
John Skaller
ska...@internode.on.net