From: Peter Seibel
Subject: Order of evaluation in ROTATEF?
Date: 
Message-ID: <m3fzhn681t.fsf@javamonkey.com>
So I was meditating on the behavior of ROTATEF when I came across this
puzzle. Take the following expression that rotates three elements of
an array 'a' whose indices are returned by three functions (that last
bit is just so I can see what's going where in the macro expansion.)

  (rotatef (aref a (first-idx a)) (aref a (second-idx a)) (aref a (third-idx a)))

In Allegro, I get this expansion:

  CL-USER(1058): (progn (write (macroexpand-1 '(rotatef (aref a (first-idx a)) (aref a (second-idx a)) (aref a (third-idx a)))) :pretty t :length nil) nil)
  (LET ((#:G207099 A) (#:G207100 (FIRST-IDX A)))
    (LET ((#:G207102 A) (#:G207103 (SECOND-IDX A)))
      (MULTIPLE-VALUE-BIND (#:G207101)
          (AREF #:G207102 #:G207103)
        (LET ((#:G207105 A) (#:G207106 (THIRD-IDX A)))
          (MULTIPLE-VALUE-BIND (#:G207104)
              (AREF #:G207105 #:G207106)
            (MULTIPLE-VALUE-BIND (#:G207107)
                (AREF #:G207099 #:G207100)
              (PROGN (EXCL::.INV-S-AREF #:G207101 #:G207099 #:G207100)
                     (EXCL::.INV-S-AREF #:G207104 #:G207102 #:G207103)
                     (EXCL::.INV-S-AREF #:G207107 #:G207105 #:G207106))
              NIL))))))
  NIL

Note that while the reference to the array and the index for the
left-most argument to ROTATEF are computed first, the AREF for the
left-most argument isn't performed until last. That seems a bit
strange to me--though I couldn't find anything in the spec, on way or
another, to explain why this is right or wrong. (I mostly looked at
5.1.1.1 Evaluation of Subforms to Places)

SBCL does something similar:

  * (progn (write (macroexpand-1 '(rotatef (aref a (first-idx a)) (aref a (second-idx a)) (aref a (third-idx a)))) :pretty t :length nil) nil)
  (LET* ((#:G2347 A)
           (#:G2346 (FIRST-IDX A))
           (#:G2350 A)
           (#:G2349 (SECOND-IDX A))
           (#:G2353 A)
           (#:G2352 (THIRD-IDX A)))
      (MULTIPLE-VALUE-BIND
          (#:G2345)
          (AREF #:G2350 #:G2349)
        (MULTIPLE-VALUE-BIND
            (#:G2348)
            (AREF #:G2353 #:G2352)
          (MULTIPLE-VALUE-BIND
              (#:G2351)
              (AREF #:G2347 #:G2346)
            (SB-KERNEL:%ASET #:G2347 #:G2346 #:G2345)
            (SB-KERNEL:%ASET #:G2350 #:G2349 #:G2348)
            (SB-KERNEL:%ASET #:G2353 #:G2352 #:G2351)
            NIL))))
  NIL


CLISP, on the other hand, does something more like what I naively
expected, first grabbing the array references and and index values in
the order they appear and then performing the AREF in the same order
before finally storing the values back

  [1]> (progn (write (macroexpand-1 '(rotatef (aref a (first-idx a)) (aref a (second-idx a)) (aref a (third-idx a)))) :pretty t :length nil) nil)
  (LET*
   ((#:G297 A) (#:G298 (FIRST-IDX A)) (#:G300 A) (#:G301 (SECOND-IDX A)) (#:G303 A) (#:G304 (THIRD-IDX A)))
   (MULTIPLE-VALUE-BIND (#:G305) (AREF #:G297 #:G298)
    (MULTIPLE-VALUE-BIND (#:G299) (AREF #:G300 #:G301)
     (MULTIPLE-VALUE-BIND (#:G302) (AREF #:G303 #:G304)
       (SYSTEM::STORE #:G297 #:G298 #:G299)
       (SYSTEM::STORE #:G300 #:G301 #:G302) 
       (SYSTEM::STORE #:G303 #:G304 #:G305))))
   NIL)
  NIL


Though even that leaves opens the possibility that the calls to
FIRST-IDX, SECOND-IDX, and THIRD-IDX, could side-effect A changing the
value that the AREFs will find, compared to an expanion like the
following which grabs each value out of the array as soon as possible:

  (let* ((#:g297 a) (#:g298 (first-idx a)))
    (multiple-value-bind (#:g305) (aref #:g297 #:g298)
      (let* ((#:g300 a) (#:g301 (second-idx a)))
        (multiple-value-bind (#:g299) (aref #:g300 #:g301)
          (let* ((#:g303 a) (#:g304 (third-idx a)))
            (multiple-value-bind (#:g302) (aref #:g303 #:g304)
              (system::store #:g297 #:g298 #:g299)
              (system::store #:g300 #:g301 #:g302) 
              (system::store #:g303 #:g304 #:g305))))))
        nil)

Are all these different expansions (Allegro's, SBCL's, CLISP's and my
made up one) in fact legal implementations of ROTATEF? 

I get that these are all equivalent in the absence of side-effects on
A and further that it's almost certainly a horrible idea to use place
forms that would side-effect the underlying data structure. But I was
wondering if the intent was that the order of evaluation be left open
enough to allow all these different implementations?

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Nils Goesche
Subject: Re: Order of evaluation in ROTATEF?
Date: 
Message-ID: <87ekx7n0yw.fsf@darkstar.cartan>
Peter Seibel <·····@javamonkey.com> writes:

> So I was meditating on the behavior of ROTATEF when I came across this
> puzzle. Take the following expression that rotates three elements of
> an array 'a' whose indices are returned by three functions (that last
> bit is just so I can see what's going where in the macro expansion.)
> 
>   (rotatef (aref a (first-idx a)) (aref a (second-idx a)) (aref a (third-idx a)))
> 
> In Allegro, I get this expansion:

[snip]

> Note that while the reference to the array and the index for the
> left-most argument to ROTATEF are computed first, the AREF for the
> left-most argument isn't performed until last. That seems a bit
> strange to me--though I couldn't find anything in the spec, on way or
> another, to explain why this is right or wrong. (I mostly looked at
> 5.1.1.1 Evaluation of Subforms to Places)
> 
> SBCL does something similar:

[snip]

> CLISP, on the other hand, does something more like what I naively
> expected, first grabbing the array references and and index values in
> the order they appear and then performing the AREF in the same order
> before finally storing the values back

[snip]

> Though even that leaves opens the possibility that the calls to
> FIRST-IDX, SECOND-IDX, and THIRD-IDX, could side-effect A
> changing the value that the AREFs will find, compared to an
> expanion like the following which grabs each value out of the
> array as soon as possible:

[snip]

> Are all these different expansions (Allegro's, SBCL's, CLISP's
> and my made up one) in fact legal implementations of ROTATEF?
> 
> I get that these are all equivalent in the absence of
> side-effects on A and further that it's almost certainly a
> horrible idea to use place forms that would side-effect the
> underlying data structure. But I was wondering if the intent
> was that the order of evaluation be left open enough to allow
> all these different implementations?

I don't think so; I'd say yours is the only legal one, and the
spec is quite clear on this.  Incidentally, LispWorks gets this
right:

(PROGN
  (LET* ((#:SUBFORM565 A) (#:SUBFORM566 (FIRST-IDX A)))
    (LET ((#:NEW-VALUE573 (AREF #:SUBFORM565 #:SUBFORM566)))
      (LET* ((#:SUBFORM568 A) (#:SUBFORM569 (SECOND-IDX A)))
        (LET ((#:NEW-VALUE567 (AREF #:SUBFORM568 #:SUBFORM569)))
          (LET* ((#:SUBFORM571 A) (#:SUBFORM572 (THIRD-IDX A)))
            (LET ((#:NEW-VALUE570 (AREF #:SUBFORM571 #:SUBFORM572)))
              (PROGN
                (SETF::\"COMMON-LISP\"\ \"AREF\" #:NEW-VALUE567 #:SUBFORM565 #:SUBFORM566)
                (SETF::\"COMMON-LISP\"\ \"AREF\" #:NEW-VALUE570 #:SUBFORM568 #:SUBFORM569)
                (SETF::\"COMMON-LISP\"\ \"AREF\" #:NEW-VALUE573
                                                 #:SUBFORM571
                                                 #:SUBFORM572))))))))
  NIL)

Regards,
-- 
Nils G�sche
"Don't ask for whom the <CTRL-G> tolls."

PGP key ID #xD26EF2A0