[gambit-list] Fatal crash

Marc Feeley feeley at iro.umontreal.ca
Mon Apr 14 22:53:25 EDT 2008


On 14-Apr-08, at 3:58 PM, Derek Peschel wrote:
> On Mon, Apr 14, 2008 at 02:03:37PM -0400, Joel J. Adamson wrote:
>> Is there somewhere I can read more about this?
>
> The manual doesn't mention type safety or the namespace feature
> (which the ## prefix is one instance of) but the Wiki has a page
> on them.  ## means "internal" more than "unsafe".  Many of the
> internal procedures are unsafe, though, and all unsafe procedures
> are supposed to be internal.
>
> http://dynamo.iro.umontreal.ca/~gambit/wiki/index.php/Namespaces
>
> Comments from new readers might improve the page.  That's why I'm
> posting to the list.

Here's a short explanation of the ## prefix.

Gambit is a "Scheme in Scheme", meaning that most of the code  
(including the Gambit compiler, the Gambit interpreter and the Gambit  
runtime system and libraries) is written in Scheme and compiled with  
the Gambit compiler.  OK, so how does this work?  Take something  
simple like the car procedure.  How can car be implemented in Scheme?   
It obviously can't be implemented as:

(define (car x)
   (car x))

Since this is an infinite recursion.  Gambit's implementation of the  
car procedure corresponds roughly to this:

(define (car x)

   (declare (extended-bindings))

   (if (pair? x)
       (##car x)
       (error "car expects a pair!")))

The ##car procedure which is called by car is a version of car which  
assumes that the parameter is a pair.  In other words ##car is simply  
an indirection (with offset).  Thanks to the extended-bindings  
declaration the compiler knows that the reference to the ##car  
variable truly refers to the ##car procedure, so the compiler can  
inline this operation.  The resulting C code for the body of car is  
roughly the following:

/* x is a ___SCMOBJ, which is of type int */

if (___PAIRP(x))
   result = ___CAR(x);
else
   error(...);

where ___PAIRP and ___CAR are defined in include/gambit.h as something  
like:

#define ___tPAIR 3
#define ___PAIRP(x) (((x) & 3) == ___tPAIR)
#define ___CAR(x) (___SCMOBJ*)((x) - ___tPAIR)[1]
#define ___CDR(x) (___SCMOBJ*)((x) - ___tPAIR)[2]

The original purpose of the ## prefix is to give a non-conflicting  
name for the primitive operations, such as ##cons, ##car, ##pair?,  
which are needed to implement more complex procedures such as append  
and write.  Note that the ## prefix is illegal in Scheme, so these  
names (which are accepted by the Gambit reader) will never conflict  
with variable names defined by portable Scheme code.  This is fairly  
common practice, for instance MzScheme uses the #% prefix for these  
"hidden" names.

The ## prefix has also been used to "keep out of the user's way" some  
of the runtime's utility procedures which are not meant to be called  
directly by the user.  For performance reasons, these procedures  
normally do not check the type of their parameters, and they only call  
other ## procedures.  As a general rule for most non-## procedures  
provided by the runtime system there is a corresponding ## procedure  
which does the same operation without type checks, and sometimes with  
slightly different parameters.  For example the ##append procedure  
takes exactly two parameters and it does not require that the first  
parameter is a proper list (any non-pair object is treated as the  
empty list), so (##append 11 '(22 . 33)) => (22 . 33).  This makes it  
easy to enforce one of Scheme's requirements that the redefinition of  
a predefined global variable does not change the behaviour of  
procedures which would seem to have to call them.  For example the  
naive (and incorrect) definition of list-ref is:

(define (list-ref lst i)
   (if (= i 0)
       (car lst)
       (list-ref lst (- i 1))))

It is incorrect because doing (set! = <) or (set! car cdr) would  
change the behaviour of list-ref.  Instead, list-ref is implemented  
roughly as

(define (list-ref lst i)
   (##list-ref lst i))

(define (##list-ref lst i)

   (declare (extended-bindings))

   (if (##= i 0)
       (##car lst)
       (##list-ref lst (##- i 1))))

That way a mutation of any of the global variables =, car, -, list-ref  
will not change the behaviour of the list-ref procedure:

(let ((lr list-ref))
   (set! = <)
   (set! list-ref vector-ref)
   (lr '(11 22 33) 1)) => 22

So as a general rule:

1) ## procedures are unsafe (they assume the parameters are of the  
appropriate type)
2) simple ## procedures are inlinable by the compiler (##car, ##vector- 
ref, ##fixnum.+, etc)
3) if the runtime system provides procedure P, then ##P does the same  
operation without checking the parameters

Marc




More information about the Gambit-list mailing list