From: Tim Bradshaw
Subject: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <TFB.94Sep7120427@burns.cogsci.ed.ac.uk>
[I hope the previous article I just cancelled didn't get out, sorry if
it did]

Is it possible, without block compiling things, to stop functions
returning floats from consing in order to return a value in CMUCL?
I've been trying to write code using smaller functions rather than my
usual humungous pieces of pseudo-fortran and makes life hard: if I
want to find and stamp out spurious consing, then I need to either
understand everything or to profile the code.  If I use block
compilation profiling doesn't work, if I don't it conses spuriously.

A slightly deeper question: given the semantics of CL (note *not*
Lisp-in-general) -- ie that you can redefine functions at least -- is
it really possible to avoid this sort of consing at *all*?  I never
really understand whether a declaration of a function's type (esp its
return type) allows a caller to do some quick and cons-free thing to
get the result back from it -- I suppose doing whatever block
compiling does -- or do you have to play safe because it could be
redefined with a different type later?

From: Jeff Dalton
Subject: Re: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <Cvrn8s.J1M@cogsci.ed.ac.uk>
In article <················@burns.cogsci.ed.ac.uk> ···@cogsci.ed.ac.uk (Tim Bradshaw) writes:

>A slightly deeper question: given the semantics of CL (note *not*
>Lisp-in-general) -- ie that you can redefine functions at least -- is
>it really possible to avoid this sort of consing at *all*?  I never
>really understand whether a declaration of a function's type (esp its
>return type) allows a caller to do some quick and cons-free thing to
>get the result back from it -- I suppose doing whatever block
>compiling does -- or do you have to play safe because it could be
>redefined with a different type later?

I believe that you can assume the dcl is true at least for functions
in the same file, because you can assume functions not declared
notinline won't be redefined -- you call call them directly rather
than via their symbol-function.  (At least KCL used to do this, and
I don't remember X3J13 doing anything that would rule it out.)

In any case, an implementation might adopt nonstandard semantics
or provide it as an extension.

-- jeff
From: Miles Bader
Subject: Re: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <Cvv8Eo.L0n@eskimo.com>
How about each function having two (standard) entry points, a normal one, and
an "unboxed one."  Functions calling the "unboxed" entry point would be able
to both pass unboxed args & would expect to have to deal with unboxed return
values.

When calling this second entry point, args & return values could optionally
be some sort of magic cookie (a valid lisp object, but not useful in any
wider context) that either said where the actual value was, or just implied
it (i.e., [return]argN is in float-regN).  You would have different magic
cookies for the various types that could profitably be unboxed (floats,
(unsigned-byte 32), etc.).

There could be a standard shared unboxed entry point that simply boxed any
unboxed arguments and called the normal entry point, for any functions that
weren't compiled to take advantage of this feature...

This might be a great big hairy mess, and probably would require additional
(object) code all over the place, but it seems like it would work, and it
_would_ avoid consing...  (presumably you would still return boxed values
when you had them handy, to avoid boxing previously boxed values returned
unboxed...)

Comments?

-Miles
-- 
-- 
Miles Bader / ·····@eskimo.com / (206) 842-7219
`Life is a boundless sea of bitterness'
From: William D. Gooch
Subject: Re: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <Pine.A32.3.90.940909120345.35254O-100000@swim5.eng.sematech.org>
On Fri, 9 Sep 1994, Miles Bader wrote:

> How about each function having two (standard) entry points, a normal one, and
> an "unboxed one."  Functions calling the "unboxed" entry point would be able
> to both pass unboxed args & would expect to have to deal with unboxed return
> values.

Good idea, but it's better IMO to have a single visible entry point, and 
use declarations/compiler optimization to deal with actual dispatch to 
boxed or unboxed internal functions.  This is the approach that we made 
use of and tried to get Symbolics to support fully several years ago.  
It worked nicely, but without full (released) support in the compiler, 
could not be implemented cleanly, nor become widely available.  Without 
something like this or what you suggested (which could be done directly 
using system internal math functions), FPA hardware was essentially 
worthless when it came to doing DP math.

> When calling this second entry point, args & return values could optionally
> be some sort of magic cookie (a valid lisp object, but not useful in any
> wider context) that either said where the actual value was, or just implied
> it (i.e., [return]argN is in float-regN).  You would have different magic
> cookies for the various types that could profitably be unboxed (floats,
> (unsigned-byte 32), etc.).

With the single visible entry point approach, you'd get the appearance and 
abstraction of a magic cookie, although something else would happen under 
the covers in the compiled code.

> There could be a standard shared unboxed entry point that simply boxed any
> unboxed arguments and called the normal entry point, for any functions that
> weren't compiled to take advantage of this feature...

Not exactly sure what great value this would have.  You'd need to 
have functions for explicit boxing/unboxing to make much use of your 
suggestion anyway.

> This might be a great big hairy mess, and probably would require additional
> (object) code all over the place, but it seems like it would work, and it
> _would_ avoid consing...  (presumably you would still return boxed values
> when you had them handy, to avoid boxing previously boxed values returned
> unboxed...)

I don't think it's a mess (or at least the mess is hidden under the 
compiler carpet) if you take the single entry point with declarations 
approach.
From: David Richter
Subject: Re: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <34qqmf$653@larry.rice.edu>
In article <········································@swim5.eng.sematech.org>, 
"William D. Gooch" <······@swim5.eng.sematech.org> writes:
| On Fri, 9 Sep 1994, Miles Bader wrote:
| 
| > How about each function having two (standard) entry points, a normal one, and
| > an "unboxed one."  Functions calling the "unboxed" entry point would be able
| > to both pass unboxed args & would expect to have to deal with unboxed return
| > values.
| 
| Good idea, but it's better IMO to have a single visible entry point, and 
| use declarations/compiler optimization to deal with actual dispatch to 
| boxed or unboxed internal functions.

One could easily get the "best of both worlds" as follows:

every function foo gets transformed into 2 parts: foo-boxed-args and
foo-unboxed-args.  The former simply unboxes the arguments that foo
touches and passes them to the latter.  References to foo default to
the boxed version, but the optimizer can detect when unboxed arguments
are available and optimize the call from one to foo-boxed-args to one
to foo-unboxed-args.  Anywhere that the programmer wants optimizer to
spend extra effort to do this could be flagged as such using a
compiler directive.

William Gooch also spoke of unboxed return values.  This could be
handled by passing a flag and having two return sections from
foo-unboxed-args.  This could be solved by again splitting off the
return section of foo-unboxed-args into separate funtions.

For example: (in Scheme w/ labels ignoring type errors)

(define fact
  (letrec ([f (label (n) (if (zero? n) 1 (* n (f (sub1 n)))))])
    f))

-->

(define fact
  (letrec
    ([f-root
       ; receives: unboxed int
       ; returns: unboxed int
       (label (n)
	 (if (unboxed-int-zero? n)
	     (unbox-int 1)
	     (unboxed-int-* n
	       (*compiler-fluid-let*
		 ([*arg1-boxed* #f]
		  [*ret1-boxed* #f])
		 (f (unboxed-int-sub1 n))))))]
     [f-boxed-arg-unboxed-ret
       (label (n)
	 (f-root (unbox-int n)))]
     [f-unboxed-arg-boxed-ret
       (label (n)
	 (box-int (f-root n)))]
     [f-boxed-arg-boxed-ret
       (label (n)
	 (box-int (f-boxed-arg-unboxed-ret n)))]
     [f (lambda (n)
	  (*compiler-if* *arg1-boxed*
	    (*compiler-if* *ret1-boxed*
	      (f-boxed-arg-boxed-ret n)
	      (f-boxed-arg-unboxed-ret n))
	    (*compiler-if* *ret1-boxed*
	      (f-unboxed-arg-boxed-ret n)
	      (f-root n))))])
    f))

Where *compiler-if* is a compile time branch on a compiler variable
(sort of like C's #ifdef) determining what code to generate; it could
certainly be internal to the compiler, and not programmer accessible.
Similarly, *compiler-fluid-let* is the dynamic assignment to the
compiler variables listed within the scope of the body of the let.
The optimizer should be able to (sometimes) detect when an argument
would otherwise be passed or returned boxed need not be boxed, and
could certainly listen to compiler directives near the call site.

Since _every_ function call in the body of fact is either known or a
primative, all the calls (other than possibly the recursive call in
f-root) can be compiled into local jumps with arguments passed in
registers.

I think I may have made this more complicated than it is. :-)

dr
From: Kelly Murray
Subject: Re: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <34qju4$ng2@sand.cis.ufl.edu>
In article <················@burns.cogsci.ed.ac.uk>, ···@cogsci.ed.ac.uk (Tim Bradshaw) writes:
|> 
|> Is it possible, without block compiling things, to stop functions
|> returning floats from consing in order to return a value in CMUCL?
|> I've been trying to write code using smaller functions rather than my
|> usual humungous pieces of pseudo-fortran and makes life hard: if I
|> want to find and stamp out spurious consing, then I need to either
|> understand everything or to profile the code.  If I use block
|> compilation profiling doesn't work, if I don't it conses spuriously.

The simple solution is to declare the smaller functions inline
and then there won't be any passing/returning of floats.
Of course this increases your code size.  Might not matter.

-- 
- Kelly Murray  (···@prl.ufl.edu) <a href="http://www.prl.ufl.edu">
-University of Florida Parallel Research Lab </a> 96-node KSR1, 64-node nCUBE
From: Thomas M. Breuel
Subject: Re: Stopping fp functions consing in CMUCL
Date: 
Message-ID: <TMB.94Sep10045051@arolla.idiap.ch>
In article <················@scott.cogsci.ed.ac.uk> ···@cogsci.ed.ac.uk (Tim Bradshaw) writes:
|In article <················@arolla.idiap.ch> ···@arolla.idiap.ch (Thomas M. Breuel) writes:
|
|   Yes, there are many ways of supporting cons-free passing and returning
|   of unboxed floating point values in CL without resorting to block
|   compilation.
|
|How?  (I'm interested, not being rude!).

Basically, there are various strategies, involving different tradeoffs
between compilation speed, space, and running time.

The most naive approach is to recompile everything whenever a single
function changes.  But that is probably not acceptable for real use.

You could also delay compilation until any user-defined Lisp function
gets called from the top-level.  Actually, such an approach isn't as
weird as it may sound: you can compile to byte-codes with type
annotations right away and then compile the byte-codes to machine code
on-the-fly, taking into account all the type information available at
that point (on-the-fly compilation is part of several systems).  In
fact, the system can even take into account type statistics and
profile information during on-the-fly compilation.

Many statically typed systems figure out the minimal set (or close to
it) of recompilations necessary when some code changes.  Something
analogous could be used for recompiling numerical code as definitions
and declarations are added and change.

All of the above techniques require recompilations, and hence keeping
source or precompiled source around in some form.  This is probably
not a problem--even if you don't want this data in the Lisp system
itself, you can usually simply maintain pointers to source files and/or
put the data on disk.  This is really only an issue during development
anyway.

Without recompilations, declared functions can provide multiple entry
points, one for unboxed values another for boxed calls.  If needed,
you can usually patch callers without recompilation to take advantage
of the new entrypoints.

FUNCALL is also an important case.  I'm not sure what CLtL/2
declaration semantics are these days, but it is easily possible in
principle to provide declarations for the type of a FUNCALLable value
to be a function that has an unboxed entry point and have the FUNCALL
carry out an unboxed call.

				Thomas.