Shop OBEX P1 Docs P2 Docs Learn Events
Compiler bug? [Sunday puzzler] — Parallax Forums

Compiler bug? [Sunday puzzler]

Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
edited 2011-03-24 11:36 in Propeller 1
Before trying the following program, see if you can figure out what the output will be. Advance warning: your answer will probably be wrong.
CON

   _clkmode       = xtal1 + pll16x
   _xinfreq       = 5_000_000

OBJ

  sio:  "FullDuplexSerial"

PUB  Start | i, n

  sio.start(31, 30, 0, 9600)
  n := 10
  repeat i from 1 to n~
    sio.dec(i)
    sio.tx(" ")
Hint: the output is not what I believe it should be, viz.: 1 2 3 4 5 6 7 8 9 10.

-Phil
«1

Comments

  • jazzedjazzed Posts: 11,803
    edited 2011-03-20 12:08
    Wild guess: Prints "1 " and executes whatever happens to be in memory after the repeat loop.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 12:16
    Nope.

    -Phil
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2011-03-20 12:23
    My guess for incorrect behavior would have been,
    1 0
    but I tried it and see that I'm wrong.
  • MicrocontrolledMicrocontrolled Posts: 2,461
    edited 2011-03-20 12:27
    Well, "~" assigns the value 0, so it would it be "1 0 " right?

    EDIT: I tried it out, and I was wrong, so now I'm so puzzled by the result that I'm probably going to thinking about it all day.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 12:30
    Tracy, Micro,

    My thought was that, since the ~ operator is a post-clear, that the original value would be grabbed for the repeat limit before the clear takes place. But, apparently, we're wrong. :) (Actually, I think it's the compiler that's wrong.)

    For example, in this code:
    a := 10
    p := a~
    

    p is assigned the value 10.

    -Phil
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 12:33
    Martin,

    Nope.

    -Phil
  • Martin_HMartin_H Posts: 4,051
    edited 2011-03-20 12:38
    My guess is that it prints nothing.
  • jazzedjazzed Posts: 11,803
    edited 2011-03-20 12:40
    Very strange result. Also happens with BSTC.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 12:40
    Martin,

    Nope.

    -Phil
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 12:43
    jazzed,

    I know what you mean. I've yet to come up with a plausible explanation for the output.

    -Phil
  • jazzedjazzed Posts: 11,803
    edited 2011-03-20 12:45
    Try it with step -1
  • Martin_HMartin_H Posts: 4,051
    edited 2011-03-20 12:55
    That is really weird.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 12:55
    jazzed,

    It gives the right result with step -1, same as with repeat i from 1 to 10 step -1.

    -Phil
  • jazzedjazzed Posts: 11,803
    edited 2011-03-20 13:04
    jazzed,

    It gives the right result with step -1, same as with repeat i from 1 to 10 step -1.

    -Phil

    Well at least it works ! :)
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2011-03-20 13:08
    Actually, I think the output makes perfect sense if you realize that the limit n and the step size/direction is evaluated each time around the loop, not just at the outset.
    i is one, n is 10, step is implied +1, n becomes zero
    i is two, n is 0, step is recalculated -1
    i is one, n is 0, step -1
    i is zero, n is 0, step -1
    loop over and out
  • jazzedjazzed Posts: 11,803
    edited 2011-03-20 13:15
    Propeller Manual: "In the syntax 2 form, REPEAT is actually always using a Delta value, but when the “STEP Delta” component is omitted, it uses either +1 or -1 by default, depending on the range of Start and Finish."

    Guess that explains Tracy's observation. I would have never expected the step to change though.

    Think Different! :)
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2011-03-20 13:34
    The manual shows an example where the step size is recalculated within the <repeat i from m to n> loop, but it does not explicitly say whether or not the limit and direction are evaluated each time around. Evidently they are. Good to know!

    repeat n
    in that case the manual states explicitly that n is evaluated only at the outset.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 13:41
    Tracy,

    You're probably right, but I would contend that the limits should not be recalculated every time through the loop, but just once before the loop starts. Anyway, the way this came up was in flush routine for a buffer:
    repeat i from 0 to ptr~ - 1
      tx(buf[i])
    
    It would have made a convenient idiom, had it only worked in The Only Way That Makes SenseTM. Oh, well!

    -Phil
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-20 13:58
    I thought the answer would be "1 2". Of course, the index is checked against the upper and lower limits after it is incremented/decremented, so it a loops a few more times. Yeah, the repeat-from-to loop has some weird quirks. I would have expected the expression "repeat i from 0 to -10 step -2" to produce "0 -2 -4 -6 -8 -10", but it only produces 0. I would need to do "repeat i from 0 to -10 step 2" to get the other output.
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2011-03-20 14:54
    Phil,

    It belongs in "tricks & traps" because it enables both. It should really be documented in the Prop manual. Thanks for bringing it up. It is something I'd wondered about, but the manual was not explicit. I don't know what the expectation would be from other languages, but it is one of those things, assume nothing that requires reading between the lines.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 15:44
    This, combined with the auto-step-direction, makes the repeat semantics a complete cock-up, as far as I'm concerned. As to the auto-step-direction stuff, when you encounter repeat i from 1 to n, and n is zero, you really don't want the loop to execute at all. That's the most useful way, since it saves an extra if statement.

    Tracy,

    I've been looking at your loop trace some more:
    i is one, n is 10, step is implied +1, n becomes zero
    i is two, n is 0, step is recalculated -1
    i is one, n is 0, step -1
    i is zero, n is 0, step -1

    and one thing didn't quite add up for me. Where is the comparison made to the limit, I wondered? IOW, how did the 2 get there? As it turns out, the limit expression is not even computed until the loop has executed once! In my example n == 10 the first time through the loop, even though the expression n~ occurs at the beginning. That's completely screwed up.

    Here's another pathological example:
    CON
    
       _clkmode       = xtal1 + pll16x
       _xinfreq       = 5_000_000
    
    OBJ
    
      sio:  "FullDuplexSerial"
    
    PUB  Start | i, n
    
      sio.start(31, 30, 0, 9600)
      repeat i from 1 to func
        sio.dec(i)
        sio.tx(" ")
    
    PUB func
    
      sio.tx("*")
      return 5
    
    Here's the output:
    1 *2 *3 *4 *5 *

    Why would anyone want the function that computes the limit evaluated more than once, and at the end of the loop, rather than at the beginning? 'Not sure what Chip was thinking on this one.


    -Phil
  • potatoheadpotatohead Posts: 10,261
    edited 2011-03-20 19:47
    What if one wanted to drop through the loop on condition met?

    That's generally how it would be done in PASM, why not SPIN? I'm quite sure that's what Chip was thinking.

    Seems to me those boolean expressions we mentioned earlier would work nicely for that use case. Efficient too.
  • wmosscropwmosscrop Posts: 409
    edited 2011-03-20 19:49
    I would contend that the limits should not be recalculated every time through the loop, but just once before the loop starts.

    Phil, C# behaves in a similar manner. For example,
    int j = 10;
    for (int i = 0; i < j--; i++)
    {
         Console.WriteLine(i);
    }
    
    Outputs 0 1 2 3 4, not 0...9.

    In both cases, the limit is not a constant; it is an expression. I consider it to be a feature that I can use an expression in this manner. In addition, I would also expect to have a variable re-evaluated for each iteration.

    Why?

    - It allows for the manipulation of the variable, or variables used by the expression, from any cog.
    - An additional memory location would be needed to store the "frozen" value of the limit for variables and expressions.

    I do see your point... and I have worked with languages that do set up the limit at the beginning of the loop.

    Walter
  • jazzedjazzed Posts: 11,803
    edited 2011-03-20 20:38
    int j = 10;
    for (int i = 0; i < j--; i++)
    {
         Console.WriteLine(i);
    }
    
    That is very explicit compared to the repeat statement syntax.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 20:38
    potatohead wrote:
    What if one wanted to drop through the loop on condition met?
    'Not sure what you mean by this. Can you elaborate?

    -Phil
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 20:42
    Walter,

    I've no experience with C#, but I'm sure the test must get performed before the loop executes the first time, as is done in Perl, which has a similar construct.

    -Phil
  • potatoheadpotatohead Posts: 10,261
    edited 2011-03-20 22:20
    Two use cases:

    One, need to exit loop on condition = true. Set limit equal to a value that would assure exit, and the program flow "drops out of the loop", onto the next instruction, following the loop construct. If the boolean math were used, as discussed on the earlier thread ">=", that test could be rather complex, without a nest of if-then constructs.

    Another would be a limit that's a function of some other variables. Evaluating that limit each time permits the same simple repeat construct to handle many cases, where a fixed limit would require extra logic to complete. Honestly, not having this would make a case for a GOTO. That's how it would be in PASM... Complex case, but sometimes those are difficult with rigid structures.

    On the matter of evaluating at beginning or end, I think that's just a matter of preference. I like end evaluations, just because that makes sense in PASM because we have DJNZ, for example. This is a lot like counting from 0 or 1 really, or big endian vs little endian.

    Just trade-offs, IMHO.

    A pre-execute evaluation doesn't require a condition to enter the loop, as that evaluation contains the condition, meaning the loop could fail right away, and not do anything, right? A post does require that condition, because it's not evaluated until the end, meaning it's going to execute at least once. (assuming the loop is to be conditionally executed)

    For me personally, it seems to align with PASM, because we have the DJNZ instruction, for example. I would be very likely to test before entering the loop, because I would also want to leverage that instruction in the loop.

    I'm not saying it's better, just that it makes sense to me. I can see the use cases for this behavior, and the alignment to PASM, as favorable, because the use cases for the language elements are not as rigid, and that's generally a good thing for me personally.

    I don't see a strong argument for it being one way or the other, and finally, my default assumption would be for all the parameters of the loop to be "live", not static, as that's been my experience programming other things, and it can easily be done in PASM that way, and is often more efficient in terms of storage done that way too.

    Edit: I do agree completely with both including it in "tips and traps", and clarifying documentation with a few more statements and use cases to illustrate what does happen. This has been a good thread in that respect.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 22:51
    potatohead wrote:
    One, need to exit loop on condition = true. Set limit equal to a value that would assure exit, and the program flow "drops out of the loop", onto the next instruction, following the loop construct. If the boolean math were used, as discussed on the earlier thread ">=", that test could be rather complex, without a nest of if-then constructs.
    That's what quit accomplishes so succinctly. It's generally poor programming practice to reassign finite loop variables within the loop. But, notwithstanding that, wouldn't setting the more volatile iterator variable to the loop end accomplish the same thing?

    Also, if the limit evaluation is done at the end of the loop, the loop construct should look more like this:
    [b]repeat[/b] i [b]from[/b] 1
      do stuff
    [b]to[/b] n
    

    That mirrors the pre- and post-conditional forms of while and until. It's simply wrong to have an expression that appears at the beginning of the loop not get evaluated until the end. There's really no logical way to rationalize the way it's done in Spin. Form should always follow function.

    BTW, I can no advantage in having Spin loop constructs mirror those of PASM in any way. That's what high-level languages are for: to insulate programmers from those very hardware details.

    -Phil
  • potatoheadpotatohead Posts: 10,261
    edited 2011-03-20 23:21
    Well, there is where we differ.

    Quit works, except when you want the loop to complete, and not execute again, upon condition met. So that's just a trade-off. In the case of adjusting the variable, one could use QUIT, then reproduce a bit of code to complete the loop in the QUIT handler. Or, just write knowing the loop will finish. Either works for a lot of cases, and doesn't work so well for other cases, so it's just better to know how it works, because it works how it works, and that's that.

    Having dynamic limits is a valid use case. Seems to me, things get more complex in that use case, without this behavior. Arguably, the majority of cases will be with fixed limits, but there again, trade-offs. Better to know how it works, and leverage that to get things done.

    Part of the attraction of SPIN to me, is that alignment! SPIN can be very "assembly like", if somebody wants it to be. I noted that early on. This is a good thing. The REP instruction in Prop II will bring some of the structure the other way too, also a good thing. Overall, those two data points suggests to me that Chip is thinking a greater synergy between the two is a good thing overall.

    I do not disagree. I found PASM clean, and fun, and mostly easy for the easy stuff. The same was true of SPIN. Lean is the driving design principle here, and I like it.

    It also seems to me, here in embedded / micro land, where there are often a lot of constraints, the "assembly like" elements of SPIN are a real bonus, because it can be "bent to fit" when needed. I think that matters more than purity of form and function. (I do appreciate that, by the way, and am not intending to devalue your preference.)

    Maybe that goes back to my UNIX roots. Where it's possible to do absolutely bad things, it's also possible to do brilliant things. I want the brilliant things on the table, because they are often worth the bad things. I've made a lot of money doing brilliant things. Seems again, here in embedded land, that's going to come up too, isn't it?

    One final analogy, the video generator. It's one of these possible to do bad and brilliant things, because it's flexible, and somewhat loosely defined. Well, what did we get? We got some trouble figuring out some of the more advanced cases possible, but once we did, we got some really great, what I would easily characterize as "above and beyond the original design scope" code too! That's never a bad thing, even if it does require stumbling on the way to get there.

    A few things could have been done to make it considerably easier to use, and doing those things would have taken some excellent capability off the table, and we are not done with that thing just yet. It will do more than we've seen, but it's going to take dealing with the bad to get to the good.

    I will also say this. Chip hates typing in things more than once. The fact that SPIN uses indentation as it's delimiter for structured constructs is no accident. That takes the syntax you put here off the table. SPIN is lean. Very lean. This behavior falls in line with that, IMHO.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-20 23:32
    potatohead wrote:
    I will also say this. Chip hates typing in things more than once. The fact that SPIN uses indentation as it's delimiter for structured constructs is no accident. That takes the syntax you put here off the table. SPIN is lean. Very lean. This behavior falls in line with that, IMHO.
    'Not sure what you meant by that, since post- while and until are also unindented:
    repeat
      do stuff
    until i > n
    
    Maybe I missed the point of what you were trying to say.

    -Phil
Sign In or Register to comment.