#79461 - batblaster - Thu Apr 13, 2006 8:53 pm
Hi,
i have updated my compiler chain to 4.10 the one released with DevKitArm18 and i found a strange problem, i made a small test to check it and yes it's a problem, i'm using No$GBA do see it and appear to go in a infinite loop...
easy to reproduce...
Code: |
init some vars like:
u8 out=0;
u8 dly=0;
start a code and init the vbl interrupt, after:
while(!out)
{
if(vbl_intr)
{
if(dly++>120) out=1;
}
vbl_intr=false;
}
print_out("i'm here\n");
you can't reach this line..
|
you will nerver go out from the while , why ??? this code , of course, work fine with the 4.02 version of the compiler...
If someone can check i'm so happy...
Thanks...
_________________
Batblaster / 7 Raven Studios Co. Ltd
------------------------------------------
#79469 - Cearn - Thu Apr 13, 2006 10:02 pm
Is vbl_intr volatile? If not, the whole loop is optimised out.
Edit for more detail.
I'm assuming that vbl_intr is a non-volatile variable, updated by the VBlank isr, and starts out as false. At the start, you enter the loop as out is false (btw, ints if you can, bytes/halfwords if you have to). As vbl_intr is false, the dly if never happens. As vbl_intr stays false inside the loop scope, the check will never happen, so out will always be false, so the loop comes down to an endless loop.
Unless vbl_intr is volatile, in which case anything goes.
Additionally, the point of using interrupts for vsyncing is so that you can avoid the busy-wait loop by using VBlankIntrWait() to shut down the CPU and thus save power. If you're not using that, then you might as well check REG_VCOUNT or REG_DISPSTAT.
#79541 - batblaster - Fri Apr 14, 2006 5:43 am
Hi,
all you say is right but believe me if you compile all with the 4.02 , and previous all work fine... Only with 4.10 i get this problem... This is an example if you will compile this small source you can see didn't work well...
Cu..
_________________
Batblaster / 7 Raven Studios Co. Ltd
------------------------------------------
#79562 - Dwedit - Fri Apr 14, 2006 10:16 am
Not properly declaring things as "volatle" is a bug on the programmer's side, not the compiler.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#79583 - batblaster - Fri Apr 14, 2006 12:37 pm
I don't think is my problem , i try many different things including somethings like this:
for(i=0;i<120;i++)
VBlankIntrWait();
this loop didn't finish but work very well with gcc 4.02 , if is my problem because i code bad why work well with 4.02 and can't with 4.10 ???
_________________
Batblaster / 7 Raven Studios Co. Ltd
------------------------------------------
#79585 - keldon - Fri Apr 14, 2006 12:42 pm
If you have not declared the variable as volatile then the compiler can treat it any way it likes. So one version could take it one way, one version could take it another way - it does not need to be specified by the compiler as not declaring it as volatile means you do not care if it is. Declaring as volatile tells the compiler that it CAN be changed by something other than the loop.
#79605 - Cearn - Fri Apr 14, 2006 2:33 pm
From your thread at the HAM forum:
Code: |
int main(void)
{
u8 out,dly;
ham_Init();
ham_StartIntHandler(INT_TYPE_VBL,(void *)&vblFunc);
// Loop
out=0;
dly=0;
while(!out)
{
// It's a new frame?
if(g_NewFrame)
{
if(dly++>120) //if you will comment this line the code work and go out from the loop...
{
out=1;
}
// Frames isn't new anymore
g_NewFrame=FALSE;
}
}
ham_VBAText("I'm out\n");
return 0;
} |
And as this is very similar to the structure in every HAM sample:
Code: |
u8 g_NewFrame= 0;
void vblFunc()
{
g_NewFrame= 1;
}
|
The point we've been making is that g_NewFrame isn't volatile. Without that, the compiler assumes that the variable can only be modified locally in the code, in this case the while(!out) loop. Because it's never set to 1 here, the if(g_NewFrame) can't ever happen, so that out can never be set to 1 either and the whole while acts as an endless loop.
It should never have compiled any other way; the only reason older compilers allowed it is because their optimizers didn't do a proper job.
At this point, there are 3 possible solutions:
1) define g_NewFrame as volatile, which tells the compiler it can be modified from anywhere at any time and is unavailable for optimisation.
Code: |
volatile u8 g_NewFrame= 0; |
2) screw the whole interrupt thing and check via REG_VCOUNT, as the current structure essentially vsyncs via a busy-wait loop anyway.
Code: |
// NOTE: check for the definition of REG_VCOUNT
void vid_vsync()
{
while(REG_VCOUNT >= 160); // wait till VDraw
while(REG_VCOUNT < 160); // wait till VBlank
}
int main(void)
{
int ii; // ints where you can
ham_Init();
// wait 120 frames
for(ii=0; ii<120; ii++)
vid_vsync();
ham_VBAText("I'm out\n");
return 0;
}
|
Notice how much simpler the code is now.
3) Use interrupts and use VBlankIntrWait(), which is the preferred method, though I'm not sure if HAM's interrupt dispacher is properly set up for it. Probably not. In fact, I'm not even sure if it even has BIOS routine support.
Code: |
// extra stuff that may or may not be required :/
#ifndef(__thumb__)
#define swi_call(x) asm volatile("swi\t"#x ::: "r0", "r1", "r2", "r3")
#else
#define swi_call(x) asm volatile("swi\t"#x"<<16" ::: "r0", "r1", "r2", "r3")
#endif
#define REG_IFBIOS (*(vu16*)0x03007FF8)
#define IF_VBLANK 0x0001
void VBlankIntrWait() { swi_call(0x05); }
// we now continue with our regular programming
void vlbFunc()
{
// hope this is correct; it's been a while since I had to do this manually :P
REG_IFBIOS = REG_IF & IF_VBLANK;
}
int main()
{
int ii;
ham_Init();
ham_StartIntHandler(INT_TYPE_VBL, vblFunc); // (void*) cast should not be required
// wait 120 frames
for(ii=0; ii<120; ii++)
VBlankIntrWait();
ham_VBAText("I'm out\n");
return 0;
} |
Not 100% if the last one will work immediately with HAM, but you should be able to get it working with a little tinkering. If not, use the vid_vsync() version.
#79712 - batblaster - Sat Apr 15, 2006 3:39 pm
Did you try all with 4.10 ???
very strange...
All work fine with 4.02 with or without using ham, my project didn't use any lib except some map scroll routines... I'm using the Bios calls because nintendo say is required to save power, i only say is strange a simple loop work and was compiled well with 4.02 and previous and didn't work with 4.10
I'm using already in my project the VBL you mean thanks i'm only wrote the question for the community , if someone switch from 4.02 to 4.10 and didn't have the code clear maybe can have problem...
I also wrote it in the ham forum because there there are many users who can have the same problem...
That's all...
Thanks...
_________________
Batblaster / 7 Raven Studios Co. Ltd
------------------------------------------
#79715 - Cearn - Sat Apr 15, 2006 4:27 pm
batblaster wrote: |
Did you try all with 4.10 ??? |
Yes I did. And the solution that's been suggested three times now works: make g_NewFrame volatile.
batblaster wrote: |
very strange... |
No, it's not. We've already explained why it's supposed to fail: g_NewFrame is never never changed inside the loop, so code that rlies on it being one can never be executed and is therefore thrown out.
From the generated assembly:
Code: |
ldrb r2, [r1] @ read the global gNewFrame
mov r3, #0 @ init dly
.L120:
cmp r2, #0 @ if gNewFrame==0, goto .L120
beq .L120 @ -> lock up
cmp r3, #120 @ if dly>120, jump out
bhi .L112
.L121:
add r3, r3, #1 @ dly++;
lsl r3, r3, #24 @ - wasteful instructions to keep dly as a byte
lsr r3, r3, #24 @ /
mov r2, #0 @ \
cmp r2, #0 @ - if gNewFrame==0, goto .L120
beq .L120 @ /
cmp r3, #120
bls .L121 @
.L112:
@ rest of code
|
The first instruction loads gNewFrame into r2. The two instructions after .L120 check whether r2 (=gNewFrame) is 0, which it will be, and then jump back to .L120, since the contents of r2 are never changed in these two instructions it forms an endless loop. If it ran in 4.02 (which it doesn't actually), then the compiler was in error.
batblaster wrote: |
I'm using the Bios calls because nintendo say is required to save power |
Perhaps, but where did you use it? If you did it in the place of that dly check, the code never gets there. VBlankIntrWait() is supposed to be used instead of gNewFrame, not in tandem. If you did it like I showed in a previous post then, yes, that might be considered strange.
#79733 - tepples - Sat Apr 15, 2006 5:41 pm
Cearn wrote: |
VBlankIntrWait() is supposed to be used instead of gNewFrame, not in tandem. |
Is it a bad habit from NES programming? On the NES, there is no wait-for-interrupt instruction, and there is no VCOUNT, so most games have the vblank ISR set a flag that the main program spin-loops on.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.