← Back to team overview

ooc-dev team mailing list archive

Member function pointers, closures, ffcall, JIT trick, more keywords, hi google search.

 

Hi everyone,

Someone on #ooc-lang brought up the 'member function pointer' issue again,
and so I have to summarize the situation here.

so about member functions pointers / closures, as I see it we have 3
alternatives now

   1. use a struct with pointers to locals: no JIT, no extra memory, as fast
   as it gets, but works only between ooc
   2. use the JIT trick I suggested weeks ago
   3. use ffcall:
   http://www.haible.de/bruno/documentation/ffcall/trampoline/trampoline.html


So here's my opinion:

   - ffcall is a very fine piece of code but seemingly unnecessary since we
   have JITs (and now's the moment you freak out and say to yourself "heck he's
   gonne all JIT again", but no)
   - Being non-compatible with C callback-registering fuctions seriously
   suck

So I was thinking implementing both solutions 1 and 2, and using 2 only when
passing member function pointers/closures to C callback-registering
functions, thus we'd have a nearly optimal case each time.

About the solutions, some implementation details.

Solution #1 requires 'function pointers' in ooc impl to become structs. This
struct would be something like:

struct FunctionPointer {
  void *locals; // points to a struct containing the local variables (e.g.
this for a member function, others for a full-blown closure)
  void (*function)(); // points to the real function (which returns anything
and takes anything as arguments, C doesn't care)
};

I think C++ guys call this kind of object a Functor.
So this lightweight structure would allow a closure/membfuncpointer to be
passed around easily in ooc code. A member function would be thought of as a
closure with 'this' (or 'self', ie. the instance) in the locals pointer.

Solution #2, I have already implemented months ago with GNU Lightning. It's
a very lightweight AND lightning fast JIT. Best thing, it's all contained in
a single C header file (all with macros, but done right), so it's really
negligible overhead to an ooc application. How it works is, it creates at
runtime a wrapper function per closure, e.g. taking no arguments, which
calls the closure with all the right arguments.

Example:

class Window {

  String title;

  func display {
    printf("Displaying window %s\n", this.title);
  }

  func new {
    glutDisplayFunc(this.@display); // reminder: the '@' is necessary or
display would be called.
  }

}

Currently generates the following C (as pseudocode to leave out the
details):

void Window_display(struct Window* this) {
  printf("Displaying window %s\n", this->title);
}

struct Window* Window_new() {
  glutDisplayFunc(this->display); // apparently don't need an '&' in modern
C
}

But it will crash in Window_display since 'this' will be a dangling pointer
(e.g. glut doesn't pass any arguments to the function).
So now the plan is to generate code like:

void Window_display(struct Window* this) {
  printf("Displaying window %s\n", this->title);
}

struct Window* Window_new() {
  glutDisplayFunc(wrap_memberfunc(this->display, this));
}

Where wrap_memberfunc would return a freshly allocated function looking like
(if it was disassembled and decompiled to C):

void Window_display_wrapper() {
  struct Window* this = 0x9108ff; // whatever was the address of this
  Window_display(this); // simplified example, a real-life virtual function
would be called with this->display(this);
}

I hope you now all understand the issue, and the solutions I suggest. I also
hope you understand why a (very very small) JIT is needed for a very
specific part of the code and for compatibility with C.

Let me know your thoughts,
Amos

Follow ups