Hi, Luigi,
I'm posting this message to the mailing list for future reference and in the hope that others might find it useful (this ought all to be properly documented anyhow!). I hope you're okay with this.
To provide some context for those just now tuning in, this is a reply to an inquiry about Objective-CL, an Objective-C bridge I have quietly been working on for the last two months, sadly completely unaware of the existence of CL-ObjC (I most probably wouldn't have started my own project had I known of CL-ObjC). Objective-CL is less featureful than CL-ObjC appears to be (for example, it doesn't let you define classes or methods yet). On the other hand, it's portable not only across CL implementations, but also across operating systems, supporting both Mac OS X and GNUstep targets and potentially lots of processor architectures. The license has not yet been decided upon (meanwhile it's GPL'd, because the GPL is the most restrictive license I'd consider using).
Darcs repository: http://matthias.benkard.de/code/objective-cl
Reference manual: http://matthias.benkard.de/objective-cl/documentation
Now, on to the reply itself.
Actually, but i have to study better your code first
I've not studied your code thoroughly yet either, so I can't be sure what I do differently from CL-ObjC, but I'll just try explaining what goes on in my code.
i d like to know (and merge) how you manage:
Okay, here goes...
The most obvoius difference between ObjCL and CL-ObjC is probably that the latter is pure Lisp, while the former is partially implemented in Objective-C for portability. This is very important, because it immediately eliminates a number of problems and makes their solution trivial (more about that later). Furthermore, I can reuse code from PyObjC in this way, so if Apple decides to switch to LLVM or something, Objective-CL can be made compatible with it easily simply by stealing the approprate code from PyObjC. :)
The only Objective-C source files that I have written specifically for Objective-CL are libobjcl.m and libobjcl.h. All the others are from PyObjC, adapted to Objective-CL.
- Method invocation (my code is slow, it calls compile for every invocation, caching the compiled functions for future invocations)
Method invocation is quite simple in principle. First, we convert all arguments into C values and reserve space for the return value, after which we call the method, discard the C values and cons up the result from the return value. This is precisely what LOW-LEVEL-INVOKE does. The actual method call is done by the Objective-C-layer function objcl_invoke_with_types. It's not a complicated function. You can find it in Objective-C/libobjcl.m (like, incidentally, all other functions in the Objective-C layer that are directly called from Lisp and implemented by yours truly).
I'm not sure I understand what exactly you're caching here. I do a bit of caching myself, namely of method signatures and selectors (the latter by using compiler macros). Interestingly, caching selector lookups is a huge performance boost, even though you might think that calling FIND-SELECTOR, which is a wrapper around a fast C function, should probably not take much time. The reason for this is that caching the selector objects avoids MAKE-INSTANCEing them anew for each method call, which takes much longer than the method call itself (around 4 times as long, IIRC).
Actually, there are two different kinds of caching going on here. Method signatures are cached by INVOKE-BY-NAME using a weak hash table, while selectors are loaded into the image at load time by transforming
(INVOKE obj :STRING-WITH-C-STRING "bla" :ENCODING 0)
into roughly
(INVOKE-BY-NAME obj (LOAD-TIME-VALUE (FIND-SELECTOR '(:STRING-WITH-C-STRING :ENCODING))) "bla" 0).
Now, this isn't quite correct, of course, because FIND-SELECTOR may fail. Because of this, the expansion is actually a bit more verbose:
(INVOKE-BY-NAME obj (LOAD-TIME-VALUE (HANDLER-CASE (FIND-SELECTOR '(:STRING-WITH-C-STRING :ENCODING)) (SERIOUS-CONDITION NIL (WARN (MAKE-CONDITION 'SIMPLE-STYLE-WARNING :FORMAT-CONTROL "~S designates an unknown ~ method selector." :FORMAT-ARGUMENTS (LIST '(:STRING-WITH-C-STRING :ENCODING)))) '(:STRING-WITH-C-STRING :ENCODING)))) "bla" 0)
This is done by the compiler macro for INVOKE. INVOKE-BY-NAME itself has a similar compiler macro. Note that a warning is issued by the generated code at load time when a selector can't be found. This is very useful, because it essentially means that typos in method names are caught at load time (which is normally close to compile time).
Note, by the way, that there are two slightly different functions for method calling. There's PRIMITIVE-INVOKE on the one hand and INVOKE-BY-NAME on the other (INVOKE is just a wrapper around the latter). They essentially work the same, but PRIMITIVE-INVOKE converts arguments in an ad-hoc way, while INVOKE-BY-NAME actually examines the method's signature in order to correctly convert arguments to C values. PRIMITIVE-INVOKE is not strictly needed, but it's very convenient to have, because it enables Objective-CL to inquire methods about their signatures, for example, without resorting to functions implemented in Objective-C.
- Struct by value argument passing and returning (i use an hack working on MacOSX x86 i should check also on GNU/Linux)
We do this by using libffi. It's quite simple: What libffi wants is an array of pointers that point to the arguments that it should pass to the foreign function, as well as a type specification which tells it the structure of these arguments. Storing a pointer to a struct from Lisp is trivial. Generating a type specification is non-trivial, but, well, the PyObjC code does it for us. :)
The other way around (structs as return values) is probably similar, but I don't think we actually support it yet. It may just magically work, or it may not. I haven't tested it at all (frankly, I haven't tested passing structs either, but I'm confident that if it doesn't work right now, it's easy to fix).
- Exception management (i dont support it)
This is next to trivial with Objective-CL's architecture. As objcl_invoke_with_types is implemented in Objective-C, it can catch exceptions easily. It then forwards them to the Lisp layer as a return value. (Note that the return value of the method invocation is stored by libffi into a previously allocated cell and _not_ returned as a return value, so we can use the return value in whichever way we like.)
- Condition protocol
There's not much of a protocol... ^^'
By the way, we don't yet support raising Objective-C exceptions from Lisp. I have no idea whether this is hard or not.
- Memory management (i ignored this subject)
Ah, memory management... This is an interesting topic. I use the trivial-garbage library in order to send `release' messages to objects that are collected by the garbage collector on the Lisp side. memory-management.lisp is very short. It's probably easier for you to read it than for me to explain the way it works. :)
A note about the hash tables used there, though: Their sole purpose is caching (again avoiding having to MAKE-INSTANCE every single return value we get from Objective-C via method calls, which would be very slow). If performance is not an issue, they can be removed without changing the behaviour of the system.
I also interested in your performance hacks like the compiler macros.
I hope that I explained them sufficiently above. Do remember, though, that the compiler macros are not merely performance boosters. They also provide a form of compile-time (load-time, actually) checking by issuing warnings when encountering unknown method names.
Note the style warning:
OBJECTIVE-CL> (compile nil '(lambda (x) (invoke x :do 10 :something 20 :weird 42))) ; in: LAMBDA NIL ; (SB-KERNEL:FLOAT-WAIT) ; ; note: deleting unreachable code ; in: LAMBDA (X) ; (MULK.OBJECTIVE-CL:INVOKE MULK.OBJECTIVE-CL::X :DO 10 :SOMETHING 20 :WEIRD 42) ; --> MULK.OBJECTIVE-CL:INVOKE-BY-NAME ; ==> ; (LOAD-TIME-VALUE ; (HANDLER-CASE (MULK.OBJECTIVE-CL:FIND-SELECTOR '(:DO :SOMETHING :WEIRD)) ; (SERIOUS-CONDITION NIL ; (WARN ; (MAKE-CONDITION ' MULK.OBJECTIVE-CL::SIMPLE-STYLE-WARNING ; :FORMAT-CONTROL "~S designates an unknown ~ ; method selector." ; :FORMAT-ARGUMENTS #)) ; '(:DO :SOMETHING :WEIRD)))) ; ; caught STYLE-WARNING: ; (:DO :SOMETHING :WEIRD) designates an unknown method selector. ; ; compilation unit finished ; caught 1 STYLE-WARNING condition ; printed 1 note #<FUNCTION {B862405}> T NIL
Actually in my todo i have other "external" tasks to complete like complete the CLOS interface and to really integrate
This is something I'm interested in, by the way. What does the mapping between CLOS and Objective-C classes look like? Is there support for Objective-C metaclasses? Do Objective-C methods and CLOS generic functions relate to each other in some way?
CL-ObjC/Emacs/Slime into XCode, providing better documentation and examples.
I don't know XCode, but integration with Gorm is a must, yeah. Oh, by the way, have you looked at the JOURNAL file in my repository? I only recently started it. Its purpose is to document some of my decisions regarding the development of Objective-CL.
Despite that i think that a merge with your work looks promising.
What I find most remarkable is that we seem to have done quite a lot of things in a very, _very_ similar way. Even the function/macro/class names are similar or even identical (OBJC-ID and DEFINE-OBJC-STRUCT, for example -- although I never actually implemented the latter).
But don't get me started on CANONICALIZE-<FOO>. Reading that makes my eyes hurt! The correct spelling is CANONIZE (or CANONISE, as I would have written it). :)
(I know, I know. English is an evolving language, and there's some dispute about this particular issue. Still... I like ranting about this kind of stuff.)
Bye, Matthias