[Openmcl-devel] New snapshots available

Gary Byers gb at clozure.com
Wed Feb 14 02:32:05 PST 2007


There are now new (070214) self-contained archives containing OpenMCL
source, binaries, and interfaces for DarwinPPC32/64, LinuxPPC32/64,
DarwinX8664, LinuxX8664, and FreeBSDX8664 available in
<ftp://clozure.com/pub/testing>.  The release notes entry says:

OpenMCL 1.1-pre-070214
- The FASL version changed (old FASL files won't work with this
   lisp version), as did the version information which tries to
   keep the kernel in sync with heap images.
- There are new interface files for all platforms.  These files
   encode some foreign type information a little differently
   than older ones did (notably information about foreign functions
   that return structures or accept structure args by value.)  The
   new .cdb files can't be used by older versions of OpenMCL; using
   older .cdb files with this version is "allowed, but not supported
   or recommended."
- Almost all of the changes in functionality since the last (061231)
   snapshots and since the CVS freeze on 070117 have to do with
   relatively obscure issues having to do with passing structures
   to foreign functions by value and/or returning structures from foreign
   function calls.

   These idioms are fairly rare in traditional C code (though it's
   fairly common to pass -pointers- to structures by reference
   and sometimes to return pointers to structures.  There are
   a few C compiler runtime routines that perform some flavor
   of integer division and return a two-element structure that
   contains "quotient" and "remainder" fields, but that's typically
   about the extent of the use of this idiom.)  The idioms are used
   much more often in Apple's Carbon and Cooca libraries and in
   some of the frameworks (CoreGraphics, CoreFoundation) that those
   libraries are based on.

   OpenMCL's FFI has provided some support for this in the past;
   notably, it's provided support for (most of the) structure-returning
   and struct-by-value conventions used on 32-bit PPC Darwin.  In these
   conventions, a foreign function that returned a structure received
   a pointer to an instance of that structure type as a first argument,
   and a function that received a structure argument by value received
   the structure's contents in 32-bit word-size integer chunks (regardless
   of the types or sizes of the structure's fields.)  Knowledge of these
   conventions was hardwired into various parts of the system (e.g.,
   the interface database), so that it was not generally possible to
   tell whether a given foreign function returned a structure type
   (or just happened to take an extra pointer argument.)

   Unfortunately, there are at least 4 other sets of conventions for
   dealing with structure arguments/return values on the platforms
   that OpenMCL runs on (and even the DarwinPPC32 conventions weren't
   fully/correctly implemented.)  OpenMCL's FFI is generally pretty
   low-level, but to the extent that it's reasonable to talk about
   "higher level" constructs (EXTERNAL-CALL, SEND, FF-CALL, #_), those
   higher-level constructs try to enforce uniform syntax and try
   to hide the platform-specific details in backend-specific functions.

   The impact of these changes should generally be pretty minimal.
   In a "higher-level" construct used to call a foreign function that
   returns a structure type, the first parameter in the call should
   be a pointer to an instance of that structure type.

   For example, if a :rect structure is defined as:

   (def-foreign-type nil
     (:struct :rect
       (:width :int)
       (:height :int)
       (:x :int)  ; x coordinate of origin
       (:y :int)))

   and a foreign function named "inset_rect" takes a rect and an integer
   delta and returns a new :rect "inset" by that delta, a call to that
   foreign function might look like:

   (rlet ((result :rect))
     (ff-call *address-of-inset-rect* result (:struct :rect) r :int delta :(:struct rect))
     ;; or, if "inset_rect" was declared in the interface database:
     (#_inset_rect result r delta))


   A callback that returns a :rect likewise should accept a pointer
   to an instance of the :rect type as a first (unqualified) argument
   and explicitly declare that it returns a (:STRUCT :RECT).

   (defcallback *address-of-inset-rect (result (:struct :rect) r :int delta (:struct :rect))
     (setf (pref result :rect.x) (+ (pref r :rect.x) delta)
           (pref result :rect.y) (+ (pref r :rect.y) delta)
           (pref result :rect.width) (- (pref r :rect.width) (* 2 delta))
           (pref result :rect.height) (- (pref r :rect.height) (* 2 delta))))

   Note that this is very similar to what's been (implicitly) supported
   on DarwinPPC32; the basic difference is that the return type
   ("(:STRUCT :RECT)") is explicitly specified (or, in the case of #_,
   specified in the interface database).  Whether the "result" pointer
   is actually passed as an argument or not is platform-dependent (on
   DarwinPPC64, the :rect structure would be "returned" by returning
   4 :int values in 4 different machine registers), but the same syntax
   can be used (and hides those details) on all platforms.

   In the examples above, we said that the (presumed source) rectangle
   was passed by value as a value of type (:struct :rect), and we let
   the FFI deal with the details.  Historically, this parameter could
   have been specified as a small unsigned integer N (denoting the
   DarwinPPC32 convention of passing the structure value a N
   native-word-size integer arguments.)  Again, there are several
   different conventions for passing and receiving structure values,
   and it's best to let the FFI decide how to follow those conventions.
   (Some of those conventions are quite complicated, and depend on
   the size of the structure as well as the types of its fields.)

   In all cases, a callback which declares a parameter to be of a
   structure type can treat that parameter as a pointer an instance of
   that structure type with fields initialized by the caller (as in
   the case of "r" in the example above.)

   In the ObjC bridge, the DEFINE-OBJC-METHOD macro has always provided
   syntax for specifiying that the method "returns" a structure. (That
   syntax is (:struct <struct-type> <parameter-name>). That continues
   to be supported.

   Apple's ObjC runtime provides different functions (#_objc_msgSend and
   #_objc_msgSend_stret) to handle the cases of sending messages which
   return non-structure and structure results.  These low-level functions
   are very sensitive to whether the structure is actually returned via
   an "invisible" first argument or not (this is only one of a few different
   conventions on some platforms.)  OpenMCL's ObjC bridge makes similar
   distinctions, but uses simple, consistent rules: a message that returns
   a structure should always be sent via SEND/STRET (or some variant of
   SEND/STRET) and should have a first parameter of type "pointer to
   returned structure type", regardless of whether or not that pointer
   is actually passed to the method implementation or just used as by
   some platform-specific code to transfer register values.)

   The end result of all of this (several weeks of bootstrapping) is
   that most things are pretty much the same, at least on DarwinPPC32;
   only foreign function calls/callbacks that involve passing structures
   by value or returning structures need change at all, and the changes
   generally involve being more explicit/declarative about what's going
   on.  These changes -do- allow these idioms to be used on other
   (64-bit) platforms, and since they're heavily used in Apple GUI
   libraries and since 64-bit versions of Carbon and Cocoa are announced
   features of Leopard, it seemed appropriate to get support for this
   stuff into the FFI on those platforms and to try to do it in a way
   that hid the platform-dependent details.  (I didn't expect all of
   this to take so long.)

- The initial listener PROCESS now persists across SAVE-APPLICATION.
   This means that (for instance):

   ? (defvar *listener-process* (current-process))
   *LISTENER-PROCESS*
   ? (save-application "new.image")
   shell> openmcl new.image
   ? (eq (current-process) *listener-process*)
   T
   ;; though of course the underlying OS thread, stacks, etc are unlikely
   ;; to be "equal" in any sense.

   The current process is sometimes used to mark "ownership" of thread-private
   hash-tables and streams.  (Even though it doesn't make much sense for
   STREAMs to persist across SAVE-APPLICATION, it does make sense for
   HASH-TABLEs to do so; HASH-TABLES created with the :PRIVATE T option
   and "owned" by the initial listener process continue to be owned by
   that the current listener process in the new image.)

- All of the FFI changes above do seem to allow the Cocoa IDE example
   to run on ppc64/x86-64 (as well as ppc32) under Leopard, and
   hopefully that'll soon be true of applications generated via Mikel
   Evins' Bosco system as well.  The bridge and demo code have been
   conditionalized to support ObjC 2.0 on 64-bit systems, to avoid
   deprecated functions and methods, and to support 64-bit Cocoa
   changes.  Hopefully, this has been done in a way that doesn't break
   PPC32 Cocoa under Tiger (he said, quickly rushing to the nearest
   PPC32 Tiger machine and breathing a sigh of relief when the Cocoa
   listener appeared ..)  64-bit Cocoa sometimes used 64-bit signed and
   unsigned integers in place of 32-bit integers; accordingly, the
   foreign types :<NSI>nteger and :<NSUI>nteger are defined (as 32-bit
   signed/unsigned integers) on 32-bit platforms, and these types are
   used in some method and type definitions.  (Those integer types are
   predefined in Objc 2.0, and are 64 bits wide on 64-bit platforms.)

   More pervasively (and a little more problematically), CoreGraphics
   (and things built on top of it, including Cocoa) uses double-floats
   instead of single-floats for many things on 64-bit hardware; the
   difference is abstracted (a little) via the new CGFloat type.
   This means that (for instance) code which initializes a constant-sized
   NSRect on a 32-bit machines and has traditionally done so via
   something like:

   (ns-make-rect 0.0 0.0 500.0 200.0)

   now needs to do something like:

   (ns-make-rect (float 0.0 ccl::+cgfloat-zero+) ..)

   in order to compile and run on both 32-bit and 64-bit platforms.

   where ccl::+cgfloat-zero+ is defined as 1.0f0 on 32-bit platforms
   and as 1.0d0 on 64-bit machines.  Cases involving constants won't
   incur any runtime overhead and the occasional runtime overhead in
   other cases -probably- isn't that great in context (compared to
   initializing a view hierarchy ...)  but it's certainly ugly to
   look at.  It's possible that some of this ugliness could be
   hidden in the bridge/FFI (by making them do the necessary coercions
   for you), but there are tradeoffs there.

- The ObjC bridge has had a long-standing bug whereby a standalone
   Cocoa application may have needed to find the interface databases
   at runtime in order for MAKE-OBJC-INSTANCE and MAKE-INSTANCE of
   an ObjC class to work.  (These functions needed to be able to
   send an "init" message to the newly-allocated instance, and needed
   to know the type signature of that init message in order to do that.)
   The current scheme tries to avoid this by pre-compiling helper
   functions to enable calling all known "init" message signatures.
   (More accurately, all fixed-argument "init" message signatures.)
   This scheme avoids the need to send messages whose argument
   and result types are computed at runtime (via %SEND), and %SEND
   (a) was known to be inefficient and (b) would have a lot of
   difficulty handling all known structure return/passing conventions
   on supported platforms.  Accordingly, %SEND has been deprecated
   (with extreme prejudice, e.g., removed.)

- a couple of little functions are defined (but their names are
   not yet exported) on x86-64: ccl::rdtsc and ccl::rdtsc64 provide
   access to the values returned by on-chip cycle counting instructions.
   For instance:

? (let* ((start (ccl::rdtsc)))
     (sleep 1)
     (- (ccl::rdtsc) start))
1995065244

   Hmm.  Apparently, the 2.0GHz MacBook I tried that on is actually
   a 1.995GHz MacBook.

   There are all kinds of ways for rdtsc to lose (and return
   inaccurate or misleading results): the cycle counters for
   each CPU core in a multi-core system aren't necessarily
   kept in sync, and many modern systems allow CPU clock rates
   to vary (for power-management reasons) and/or allow the CPU
   to sleep/hibernate.  OSes seem to offer some support for
   compensating for these effects, and it seems like ccl::rdtsc
   and ccl::rdtsc64 can be used to obtain interesting results.

   The RDTSC instruction actually returns an unsigned 64-bit
   result; apparently, some Intel documentation claims that this
   value will not "wrap around" to 0 at contemporary clock rates
   for at least 10 years after the system was booted.  (If you can
   keep an Intel system running for 9 years between reboots, you
   might consider telling Intel that the RDTSC counter wrapped around
   a year early; they might give you a refund.  Or maybe not.)
   A non-negative OpenMCL64 fixnum is limited to 60 bits; the
   ccl::rdtsc function truncates the 64-bit counter value so
   that it fits in a non-negative fixnum; if the 10 year limit
   for the 64-bit value is accurate, the 60-bit value would
   wrap around after about 223 days of uptime.

   ccl::rdtsc64 returns the full 64-bit counter value, but
   may return a bignum after 223 days of uptime.

- lots of bug fixes (not all of which involved the FFI or ObjC
   bridge.) 
----------------------------------------------------------------

Needless to say, I'm sorry that this took several times longer than
the "few days" that I thoght it would.



More information about the Openmcl-devel mailing list