On 11/09/2014 03:20, Marvin Humphrey wrote:
+1, this is a better solution. It should allow NULLs, though.
if (obj != NULL && !obj->klass->itables[Futzer_INTERFACE_ID]) {
Yes, and it should also INCREF the result.
I feel like the name could be shorter. How about generating a `toCLASSNAME`
macro for every Clownfish interface (and every class) which performs a safe
runtime cast? We'd have to accept `void*` which is somewhat dangerous,
but oh well.
Futzer *futzer = toFUTZER(obj);
This would allow us to eliminate DOWNCAST.
Foo *foo = (Foo*)DOWNCAST(obj, FOO);
Foo *foo = toFOO(obj);
If we add a NOTNULL, it would also allow us to replace CERTIFY, at the cost of
slightly degraded error messages on NULL values (because type information
which is available to CERTIFY would not be available to NOTNULL).
Foo *foo = (Foo*)CERTIFY(obj, FOO);
Foo *foo = toFOO(NOTNULL(obj));
Sounds good.
I'm still concerned about the quadratic space complexity. It shouldn't be a
problem unless there are thousands of classes and interfaces but here are some
numbers to illustrate the non-linear growth of the itable arrays:
50 classes, 20 interfaces: 8 KB
500 classes, 200 interfaces: 800 KB
5000 classes, 2000 interfaces: 80 MB
Some other ideas (simply brainstorming):
1. Allocate the itable array on demand. Objects of many classes are never
converted to interface types. This would only require an additional NULL check.
2. Store only the interfaces a class is known to implement in a list that is
iterated for every method call. This sounds expensive, but typically, a class
only implements very few interfaces.
3. Like 2 but lookup interfaces in a hash table, either per class, per
interface, or globally. A specialized hash table could be made reasonably fast.
4. Approaches 2 and 3 could be sped up by caching the last used interface in
the object struct. Then an interface method call would look like:
static inline void
Futzer_Futz(Futzer *self) {
ITable *itable = self->itable_cache;
if (itable != FUTZER) {
itable = slow_lookup(self->klass, FUTZER);
self->itable_cache = itable;
}
char *ptr = (char*)itable + Futzer_Futz_OFFSET;
Futzer_Futz_t method = (Futzer_Futz_t)ptr;
method(self);
}
This would shift the memory overhead from the class to the object structs.
5. (The original approach I thought of in CLOWNFISH-12.) When casting an
object to an interface type, allocate "interface objects" on the heap which
contain a pointer to the object and the itable. Subsequent interface method
calls would be made using the separate interface object.
Nick