[iterate-devel] Sequence variables are initialized twice
Dear developers, the following example shows that iter initializes sequence variables twice, once with 0 and once with the start of the range minus 1. (macroexpand-1 '(iter (for i below 10) (print i))) --> (LET* ((I 0)) (DECLARE (TYPE NUMBER I)) (BLOCK NIL (TAGBODY (SETQ I -1) LOOP-TOP-NIL (SETQ I (+ I 1)) (IF (>= I 10) (GO LOOP-END-NIL)) (PRINT I) (GO LOOP-TOP-NIL) LOOP-END-NIL) NIL)) The problem with this is not that this is a little bit ugly but that one must allow negative values in declarations for the driver variable, which conflicts with (unsigned-byte 32) declarations. Could I write a new driver instead of for that fixes the problem or will I run into trouble because someteimes one of the initial values is needed for something else (the else clause, first-time-p or initially?). Moreover, the declaration is not very tight. dotimes does better on SBCL: (macroexpand-1 '(dotimes (i 10) (print i))) --> (DO ((I 0 (1+ I))) ((>= I 10) NIL) (DECLARE (TYPE UNSIGNED-BYTE I)) (PRINT I)) Best regards Bruno Daniel
Hi, although I might become an iterate developer one day, I dont really feel yet in the position to comment on the possible problems. I just wanted to add that yesterday, I was stumbling about exactly the same problematic. If there is some code to be tested etc I would be interested. Cheers, Kilian Am 14.06.2007 um 14:24 schrieb Bruno Daniel:
Dear developers,
the following example shows that iter initializes sequence variables twice, once with 0 and once with the start of the range minus 1.
(macroexpand-1 '(iter (for i below 10) (print i))) --> (LET* ((I 0)) (DECLARE (TYPE NUMBER I)) (BLOCK NIL (TAGBODY (SETQ I -1) LOOP-TOP-NIL (SETQ I (+ I 1)) (IF (>= I 10) (GO LOOP-END-NIL)) (PRINT I) (GO LOOP-TOP-NIL) LOOP-END-NIL) NIL))
The problem with this is not that this is a little bit ugly but that one must allow negative values in declarations for the driver variable, which conflicts with (unsigned-byte 32) declarations.
Could I write a new driver instead of for that fixes the problem or will I run into trouble because someteimes one of the initial values is needed for something else (the else clause, first-time-p or initially?).
Moreover, the declaration is not very tight. dotimes does better on SBCL:
(macroexpand-1 '(dotimes (i 10) (print i))) --> (DO ((I 0 (1+ I))) ((>= I 10) NIL) (DECLARE (TYPE UNSIGNED-BYTE I)) (PRINT I))
Best regards Bruno Daniel
_______________________________________________ iterate-devel site list iterate-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/iterate-devel
Kilian Sprotte wrote:
although I might become an iterate developer one day,
I don't know whether this comment relates to your proposal to find the "twice previous bug". Maybe you shouldn't start with the hardest bug. There are 2 other issues: - make Iterate for num accept the exact same keywords as Loop e.g. Loop for downfrom to (presumably easiest) - prefix clause bug e.g. cannot define both clauses (foo x) and (foo x bar y). (needs correct implementation of a trie) I took another look at the "twice previous bug" the other day (despite having -09 time). My current suspicion is that the code breaks some invariant. The previous-code-info keeps a ref and LAST to remember the for-clause's body. When PREVIOUS is seen, that body is surrounded with (setq #:previous-var xyz) and (setq previous-var zyx) with destructive modifications of list structure. But doing so, the ref and LAST pointers must be updated as well, for a second PREVIOUS splice to work. Maybe that's the invariant that gets violated? (iter (repeat 3) (for (values a b) = (floor 5 2)) (for p-a previous a) (for p-b previous b) (collect (list a b p-a p-b))) Regards, Jorg Hohle.
Bruno Daniel wrote:
the following example shows that iter initializes sequence variables twice, once with 0 and once with the start of the range minus 1.
To me this sums up to 2 questions o Why generate (let ((#:count nil/0)) (setq #:count 5) ...)? That maybe has to do with an answer to whether the following two expressions mean the same thing, i.e. whether INITIALLY can still influence clauses like FOR and REPEAT. It probably has to do with how Iterate creates bindings. A single LET* has some restrictions... (iter (with i = 5) (repeat i) (count t)) (iter (with i) (initially (setq i 5)) (repeat i) (count t)) As I found no place in the documentation about whether or not the latter form should be supported, I'm somewhat reluctant to change the behaviour. Iterate is a 18 year old package, so there could be code which depends on this. Furthermore, although let + setq does not look pretty, it is possible that advanced compilers like cmucl and sbcl, which perform some liveness analysis, will note that the 0 value is thrown away and not emit code for it. So performance critical code compiled with performance enabling compilers does not suffer from that extra initialization. CLISP does no such optimization. I don't know how others behave. o Why generate weird code where some initial value is set to 1+ or 1- what one would expect? E.g. in (for e in-vector ...), #:index is initialized to -1. "That breaks type declarations, since one would expect index to be a positive fixnum." I believe this is due to the generators. Drivers are useable as generators, and these are all translated into a sort of (for... (DO-)NEXT body) expression, so that the body can be spliced into the (NEXT ...) clause of a generator case. In other words, Iterates now deals with code that must produce the initial value upon the first call, e.g. 0 when first called. To keep the stepper code simple, since it's often nothing more than (incf #:index) with some end-test, the variable is thus initialized with -1, or (1- expression). Probably it was deemed inacceptable back when that was implemented to make the stepper code more complex, as in (setq index (if (first-time-p) #:initial-index (1+ index))). That for sure is less efficient than a -1 initialization. Now, I don't know for sure whether that's the only reason for doing so. One can then claim that if it's the only reason, Iterate could detect whether the clause is actually used as a generator, and produce different code depending on the use as generator or not. E.g. use -1 with generators, but something like (for x initially 0 then (1+ x)) when not used as generator. Well, I appreciate that people look closely at what Iterate does, but sometimes I wonder why more people come up with requirements that would make the code even more complex, and fewer people with ideas about how to make it simpler :-) Still, contributions are welcome!
Moreover, the declaration is not very tight. dotimes does better on SBCL: (macroexpand-1 '(dotimes (i 10) (print i))) (DECLARE (TYPE UNSIGNED-BYTE I)) Perhaps because on SBCL, such precise declarations matter, so SBCL goes to some length to generate them? At the time Iterate war written, all that mattered was probably FIXNUM, at best.
As far as SBCL is concerned, I'm not even sure such declarations are needed, since one would hope that cmucl/sbcl's type inference system would derive that from the code. Furthermore, loops with constant bounds are a very special case. So it's debatable whether to add declarations for such special cases. Given good type inference, sbcl/cmucl might even be able to infer best types for (repeat <formula>) expressions, and no macro-based package can generate a good type declaration for that. In short, I think the best place for handling types is the implementation's compiler. Sort of separation of concerns. Unless the user knows better than the compiler. But macro packages do never ever have the user's knowledge. Regards, Jörg Höhle.
participants (3)
-
Bruno Daniel
-
Hoehle, Joerg-Cyril
-
Kilian Sprotte