For Loop using Byte as Negative Iterator

Archives Forums/BlitzMax Bug Reports/For Loop using Byte as Negative Iterator

Mr. Goober(Posted 2014) [#1]
Just thought I'd stick this in here. Not sure if it's a bug in itself or if it is intended behavior.

When using Bytes as the iterator in for loops, using a negative Step will cause the for loop to never run:

for local i:byte = 9 to 0 step -1
    print i
next



degac(Posted 2014) [#2]
It's not a bug.
Byte is a representation of an unsigned integer in 8 bit.
So of course '-1' doesn't mean nothing.

I dont' know if the compiler ignores the 'step -1' statement... so it reads like 'For 9 to 0'


Who was John Galt?(Posted 2014) [#3]
What he said. I guess the compiler could be a bit smarter though.


Brucey(Posted 2014) [#4]
I dunno. It could be classed as a bug, because one might look at the step value as an increment or decrement amount, rather than being converted to the loop value type.

.. runs off to test it...

And interestingly, my compiler prints nothing either, so at least I'm giving the same results as the original :-p


Floyd(Posted 2014) [#5]
Here's what I said when this came up eight years ago:

http://www.blitzbasic.com/Community/post.php?topic=54950&post=613670


TomToad(Posted 2014) [#6]
odd, that bit of code compiles to

I cannot see anything there that would keep it from working. Maybe a bug with fasm?


Derron(Posted 2014) [#7]
If it was a bug with fasm, it would not happen with BCC-NG too.


bye
Ron


Who was John Galt?(Posted 2014) [#8]
I'm no x86 expert, but doesn't that assembly just show what Floyd mentions? Counter starts at 9, adds 255 every loop (instead of adding -1) and falls straight through as the result is not <=0.


H&K(Posted 2014) [#9]
Isn't
9 + 255 = 8 (modulo 256)

If so, why does the step -1 fail?


GfK(Posted 2014) [#10]
Probably because the iterator is being treated as a byte, so it's actually 255. Haven't tested but I'd imagine For local i:byte = 9 to 0 step 255 would yield the same result.


TomToad(Posted 2014) [#11]
Found the problem. First of all, if you follow the flow of the assembly file, it should reach the print statement at least once before the add byte [ebp-4],255 line. Problem is with the jle _24 line. It should read jge _24 instead. First time through it is comparing 9 against 0 and since 9 is greater than 0, and not less than or equal to, the compare fails and the loop is never entered. The compiler should use jle for positive step values and jge for negative step values, but seems that a bug in the compiler is using the wrong version with bytes.


Who was John Galt?(Posted 2014) [#12]
Good catch, Tom! Since the compiler is interpreting this as a positive step value due to limitations of the byte type, explains the confusion.


Yasha(Posted 2014) [#13]
IMO the best fix for this (if you're listening, Brucey) is not to fix it at all, but rather to improve the communication the compiler offers to the end user. BlitzMax doesn't offer nearly enough errors and I don't think it warns at all.

Consider:

- warning, error in Strict, for trying to declare a local loop iterator as anything other than int or long, because there is no legitimate use case for this anyway

- warning in Strict for using a non local loop iterator, because there are very few legitimate use cases for this

- warning for trying to use any type with an iteration potentially outside its representable integer range (i.e. not statically verifiable to be within range), hard error for any iteration that actually does go out of range or rollover

- error for trying to use an unsigned type with a negative step, because this is a logical error and who even cares what the generated code would do, it shouldn't have compiled in the first place


TomToad(Posted 2014) [#14]
There is nothing illogical about subtracting a number from an unsigned number. Doing

i = 8
i :- 1
Print i


Will logically print 7 whether i is signed or not.

There is a problem with numbers at the edges of its range, 0 and 255 with bytes, 2147483647and -2147483648 with ints, etc... When doing math where the results go across the boundary, the result will roll over and give you an unexpected result. Try for example

Local i:Int = 2147483647
i :+ 1
Print i

This will result in -2147483648, not 2147483648 as you would expect.

This can be a problem with loops. If the jge and jle bug were fixed above, then doing For Local i:byte = 9 To 0 Step -1 would result in an endless loop. When i reaches 0, then next iteration will subtract 1, resulting in a rollover to 255. So i will always be greater than or equal to 0, never allowing the compare to fail.

This is also a problem with other types. For example

For Local i:Int = 2147483640 To 2147483647
	If i = -2147483640 Then Exit 'exit endless loop
	Print i
Next


would result in an endless loop if I had not put in line 2. This compiles to
	add	ebx,1
_28:
	cmp	ebx,2147483647
	jle	_24


When you reach the end of the loop and i = 2147483647, 1 is added resulting in a rollover to -2147483648 and i will always be less than or equal to 2147483647 causing an endless loop.

As this is a bug that will affect all types, a good solution would be to add a check for rollover. So the byte loop from post #1 would compile to

_22:
	sub	byte [ebp-4],1
        jc _23
_28:
	movzx	eax,byte [ebp-4]
	cmp	eax,0
	jge	_24
_23:
...


The int loop from this post would compile to

_22:
	add	ebx,1
        jc _23
_28:
	cmp	ebx,2147483647
	jle	_24
_23:
...


You could make the check a compiler directive so that it only checks in cases where you suspect the values will be near the boundaries. Could be automatic in the case of bytes. Something like
#Rollover True 'check for rollover conditions in loops
For Local i:Int = a to b
    DoSomething(i)
Next
#Rollover False 'Disable rollover checks.