On 10/31/06, Marvin Humphrey <[EMAIL PROTECTED]> wrote:
On Oct 23, 2006, at 6:44 PM, David Balmain wrote:
>> There are costs to this approach. It requires a fair amount of
>> boilerplate code. (We might want to consider using a symbol
>> generator.) I'm also pretty sure we'll want to write some
>> bootstrapping code to prep the virtual tables at startup, because it
>> will be difficult if not impossible to resolve all aspects of
>> inheritance at compile-time.
>
> Could you give me an example of where this is difficult?
The place where it's hardest is in the definition statements for the
class data. You'd like child classes to clone the parent's table,
then selectively overwrite the slots that they don't inherit. That's
possible with bootstrapping, but I can't think of a way to do that at
compile time.
The only way to make compile-time resolution work is to copy-and-
paste each member that gets inherited. The bigger the parent class,
the more copying and pasting. And if we ever add a method to Obj,
from which all classes inherit, we have to manually add that member
to every class.
Here's how compile-time resolution with Ferret-style inheritance looks:
/* in Dog.h */
typedef struct DOG {
Animal super;
Dog_chase_cats_t chase_cats;
} DOG;
extern const DOG DOG_CLASSDATA;
/* in Animal/Dog.c */
const DOG DOG_CLASSDATA = {
{
"Animal::Dog",
(Animal_speak_t) Dog_speak,
(Animal_eat_t) Dog_eat
},
Dog_chase_cats
};
/* in Animal/Dog/PitBull.c */
const PIT_BULL PIT_BULL_CLASSDATA = {
{
{
"Animal::PitBull",
(Animal_speak_t) Dog_speak,
(Animal_eat_t) Dog_eat
},
Dog_chase_cats
},
PitBull_chase_humans
};
Actually, I like this more than the code below. To me the braces help,
not hinder. But to each his own I guess. Also, we could macrofy this
if wanted to;
#define DOG_DATA(name) {\
name,\
(Animal_speak_t) Dog_speak,\
(Animal_eat_t) Dog_eat\
},\
Dog_chase_cats
/* in Animal/Dog.c */
const DOG DOG_CLASSDATA = {
DOG_DATA("Animal::Dog")
};
/* in Animal/Dog/PitBull.c */
const PIT_BULL PIT_BULL_CLASSDATA = {
{
DOG_DATA("Animal::Dog::PitBull")
},
PitBull_chase_humans
};
That last one's getting pretty unwieldy, and we've only got a couple
members -- imagine what things start to look like with MultiReader.
We can't macrofy these, either, because functions get overridden
right in the middle of the stuff we'd like to macrofy.
Not sure what you mean by not being able to macrofy these. I guess
I've missed something.
<snip>marginally cleaner KinoSearch example</snip>
With bootstrapping, you can do this instead:
/* in Animal/Dog/PitBull.c */
void
PitBull_bootstrap()
{
memcpy(&PITBULL_CLASSDATA, &DOG_CLASSDATA, sizeof(DOG));
PITBULL_CLASSDATA.chase_humans = PitBull_chase_humans;
}
That's a lot simpler.
> I really
> don't like the idea of bootstrapping, simply because it makes it
> impossible to clean up the memory allocated during this process when
> the application exits which in turn makes it difficult to use valgrind
> to track down memory leaks.
I certainly want to be using Valgrind for that, and I don't think I
see a conflict there.
If we declare the class tables as global variables, and the
bootstrapping performs assignment but no allocation as in the above
example, Valgrind won't conflate the globals with memory leaked from
malloc.
With macros I don't think it makes that much difference but I'm sure
you'll be able to enlighten me as to where my macro example fails, in
which case, bootstrapping is fine with me. When you said bootstrapping
initially I was think memory allocation but if that isn't happening
then I don't have a problem with it.
And even if we do malloc the tables, shouldn't it be possible to
clean up everything legit using a cascade of tear-down functions?
The trick is that you have to make sure all objects are destroyed
before you remove the classdata they rely upon. I think we can pull
that off. But I don't think we have to.
This is fine in C but I'm not sure how you'd do it from the Ruby
interpreter. I wish Ruby cleaned up it's memory allocation. That is my
major gripe with Ruby. It makes very difficult to write substantial
extensions like Ferret.
As an aside, I wonder if there's a potential benefit for resolving
this stuff at compile-time in that the more that you declare as
const, the better the compiler does at optimization. I don't think
there is, though, because even if all the virtual tables are declared
as const, the compiler never knows _which_ const table it will get
pointed at during any given method invocation. So it's always going
to have to read the table pointer from the object into a register,
add the offset for that method to get at the right function pointer
in the classdata struct, and jump into code from there.
I'd like to know whether this makes a difference too. I'm guessing the
same as you that it won't have any affect.
> I'm happy to give up my beloved valgrind for this
> project if it is really necessary.
I consider Valgrind absolutely essential for development. KinoSearch
has a script ($DIST_ROOT/devel/valgrind_test.plx), which runs the
whole test suite under Valgrind and logs the output. It takes 15
minutes instead of 9 seconds to finish, but it's worth it.
I feel the same way but Valgrind + Ruby = :(. This is one of the
things that got me thinking about building a database like search
engine which runs in a separate process to Ruby as a server, but that
is a whole other story.
--
Dave Balmain
http://www.davebalmain.com/