Compiler bug? [Sunday puzzler]
Phil Pilgrim (PhiPi)
Posts: 23,514
Before trying the following program, see if you can figure out what the output will be. Advance warning: your answer will probably be wrong.
-Phil
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
Comments
-Phil
1 0
but I tried it and see that I'm wrong.
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.
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:
p is assigned the value 10.
-Phil
Nope.
-Phil
Nope.
-Phil
I know what you mean. I've yet to come up with a plausible explanation for the output.
-Phil
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 !
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
Guess that explains Tracy's observation. I would have never expected the step to change though.
Think Different!
repeat n
in that case the manual states explicitly that n is evaluated only at the outset.
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:
It would have made a convenient idiom, had it only worked in The Only Way That Makes SenseTM. Oh, well!
-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.
Tracy,
I've been looking at your loop trace some more:
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:
Here's the output:
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
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.
Phil, C# behaves in a similar manner. For example, 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
-Phil
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
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.
Also, if the limit evaluation is done at the end of the loop, the loop construct should look more like this:
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
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.
Maybe I missed the point of what you were trying to say.
-Phil