OK, the changes to GAS parsing is done. I think this should make it easier to find problems. So here's a test file with lots of errors:
eric@compaq:~/dev/tios/src/gas_test$ cat gastest.asm
mov r1
mov r1+,r2
mov r1,r2+
inv r1, r2
inv r
And here's the result from the new parsing code:
eric@compaq:~/dev/tios/src/gas_test$ tms9900-gas gastest.asm
gastest.asm: Assembler messages:
gastest.asm:1: Error: unexpected end of line
gastest.asm:2: Error: unexpected character at "+,r2"
gastest.asm:3: Error: unexpected character at "+"
gastest.asm:4: Error: unexpected character at ",r2"
Invalid register starting at "r"
gastest.asm:5: Error: missing argument
Could this be better? Maybe, but it's a lot better than before. Time to get back to floating point code.
Friday, February 15, 2013
Saturday, February 2, 2013
I've been slacking off keeping this up to date. Lately I've been working on floating point support for GCC. I've got float-to-int and int-to-float working, and I'm trying to get addition working.
While working on that I found more annoying error messages in GAS. This line:
mov r10+, r11
gives this error:
test.asm:381: Error: missing comma separator
This is not helpful, especially since there is a comma in the expected location. What's really wrong is the missing "*" character. Unfortunately, We cannot make GAS smart enough to recognize that. We could look for junk after the last expected character, but we can't do that either since the TI format allows for junk at the end of the line. We could look for white space and do something smart, but GAS squeezes that out before our code is called.
Unfortunately, the best we can do is flag the error with as much information as we can. This is more descriptive and shows what the real problem is.
test.asm:381: Error: unexpected character at "+,r11"
Another problem I have, but cannot fix without violating TI comment rules, is if we get lines like this:
mov r11, r10+
inv r1, r2, r3
The trailing invalid characters are ignored, no error will be emitted, and the assembled instructions will actually be this:
mov r11, r10
inv r1
This is totally unexpected behaviour, and confusion and anger may follow.
While working on that I found more annoying error messages in GAS. This line:
mov r10+, r11
gives this error:
test.asm:381: Error: missing comma separator
This is not helpful, especially since there is a comma in the expected location. What's really wrong is the missing "*" character. Unfortunately, We cannot make GAS smart enough to recognize that. We could look for junk after the last expected character, but we can't do that either since the TI format allows for junk at the end of the line. We could look for white space and do something smart, but GAS squeezes that out before our code is called.
Unfortunately, the best we can do is flag the error with as much information as we can. This is more descriptive and shows what the real problem is.
test.asm:381: Error: unexpected character at "+,r11"
Another problem I have, but cannot fix without violating TI comment rules, is if we get lines like this:
mov r11, r10+
inv r1, r2, r3
The trailing invalid characters are ignored, no error will be emitted, and the assembled instructions will actually be this:
mov r11, r10
inv r1
This is totally unexpected behaviour, and confusion and anger may follow.
Wednesday, January 16, 2013
GCC 4.4.0 patch 1.7
Changes this version:
Libgcc now built for TMS9900
Implemented optimized assembly functions in libgcc for:
count leading zero bits
count trailing zero bits
find index of least significant set bit
count set bits
calculate parity bit
signed and unsigned 32-bit division and modulus
Fixed 32-bit multiplies, was only doing unsigned multiply
Fixed 32-bit negation, was emitting invalid NEG instruction
Removed fake PC register (Yay!)
New build instructions to make libgcc
Fixed function prologue and epilogue, was saving R11 unnecessarily
Optimized function epilogue, saving a few cycles
Enforced correct use of R11 register, was causing randomly broken code
Download:
gcc-4.4.0-tms9900-1.7-patch.tar.gz
Libgcc now built for TMS9900
Implemented optimized assembly functions in libgcc for:
count leading zero bits
count trailing zero bits
find index of least significant set bit
count set bits
calculate parity bit
signed and unsigned 32-bit division and modulus
Fixed 32-bit multiplies, was only doing unsigned multiply
Fixed 32-bit negation, was emitting invalid NEG instruction
Removed fake PC register (Yay!)
New build instructions to make libgcc
Fixed function prologue and epilogue, was saving R11 unnecessarily
Optimized function epilogue, saving a few cycles
Enforced correct use of R11 register, was causing randomly broken code
Download:
gcc-4.4.0-tms9900-1.7-patch.tar.gz
Sunday, January 13, 2013
So, I'm back to debugging what went wrong with Rush Hour. It turns out that in the show_wall function, R11 is being used without being saved first. When the function returns, execution goes off into the woods and we're hosed.
000007f0:
7f0: 02 01 46 00 li r1, >4600
7f4: d8 01 8c 02 movb r1, @>8C02
7f8: 02 0b 40 b0 li r11, >40B0
# R11 used before being saved
7fc: d8 0b 8c 02 movb r11, @>8C02
800: 06 cb swpb r11
802: d8 0b 8c 00 movb r11, @>8C00
806: 02 01 47 00 li r1, >4700
The problem seems to be that for some reason df_regs_ever_live_p() does not reflect the usage of R11 so we don't know that we should save it.
The sequence which uses R11 was added as part of a peephole replacement, and all other registers allocated this way are not marked as used either. Apparently, GCC tries to allocate an unused register in preference to reusing one previously allocated. Interesting.
I started looking into the peephole handling code, but realized that R11 was being allocated due being designated as volatile, and all other volatile register were already used. After thinking about this a bit, I can fix this much more easily by just changing R11 to be preserved across function calls.
Ugh, still have problems. So now GCC thinks R11 is preserved, and tries to put values there to be preserved across a function call. Obviously, those values are destroyed after the return. So R11 has instead been marked as a special-purpose register and will not be considered for allocation. I don't like this, since R11 could legitimately be used if only we knew when to preserve it. At least everything works now.
I also looked into the reported stack size problem, where stack values were overwriting memory used to save register values. The problem there was that there were several places in tms9900.c which calculated which registers were to be saved, and they did not all agree. This discrepancy resulted in space for R11 being included for the prologue and epilogue, but not included for the stack variables. The register value stored in the overlap region got destroyed as a result.
The work that was done for optimizing the prologue and epilogue also included merging all the work to determine which registers to save into one function. This improved code clarity, and removed the ambiguity which lead to this problem.
As a side note, there is a libiberty library in binutils, but it's not especially useful. In the bundled README file, it is stated that this is a collection of random useful functions. It also states that these routines are highly system-dependant. It's also filled with a bunch of routines which don't seem to be very useful on a target system. I guess I still need to finish my libc implementation.
So at this point, all the bug reports have been addressed, and all my milestone goals have been reached. I suppose this is a good time to put together another release.
000007f0
7f0: 02 01 46 00 li r1, >4600
7f4: d8 01 8c 02 movb r1, @>8C02
7f8: 02 0b 40 b0 li r11, >40B0
# R11 used before being saved
7fc: d8 0b 8c 02 movb r11, @>8C02
800: 06 cb swpb r11
802: d8 0b 8c 00 movb r11, @>8C00
806: 02 01 47 00 li r1, >4700
The problem seems to be that for some reason df_regs_ever_live_p() does not reflect the usage of R11 so we don't know that we should save it.
The sequence which uses R11 was added as part of a peephole replacement, and all other registers allocated this way are not marked as used either. Apparently, GCC tries to allocate an unused register in preference to reusing one previously allocated. Interesting.
I started looking into the peephole handling code, but realized that R11 was being allocated due being designated as volatile, and all other volatile register were already used. After thinking about this a bit, I can fix this much more easily by just changing R11 to be preserved across function calls.
Ugh, still have problems. So now GCC thinks R11 is preserved, and tries to put values there to be preserved across a function call. Obviously, those values are destroyed after the return. So R11 has instead been marked as a special-purpose register and will not be considered for allocation. I don't like this, since R11 could legitimately be used if only we knew when to preserve it. At least everything works now.
I also looked into the reported stack size problem, where stack values were overwriting memory used to save register values. The problem there was that there were several places in tms9900.c which calculated which registers were to be saved, and they did not all agree. This discrepancy resulted in space for R11 being included for the prologue and epilogue, but not included for the stack variables. The register value stored in the overlap region got destroyed as a result.
The work that was done for optimizing the prologue and epilogue also included merging all the work to determine which registers to save into one function. This improved code clarity, and removed the ambiguity which lead to this problem.
As a side note, there is a libiberty library in binutils, but it's not especially useful. In the bundled README file, it is stated that this is a collection of random useful functions. It also states that these routines are highly system-dependant. It's also filled with a bunch of routines which don't seem to be very useful on a target system. I guess I still need to finish my libc implementation.
So at this point, all the bug reports have been addressed, and all my milestone goals have been reached. I suppose this is a good time to put together another release.
Saturday, January 12, 2013
I was thinking about alloc() lately, so I spent some time investigating how I could pull it off. The short answer is: I can't. The longer answer is: I shouldn't try.
Alloca is fabled for having buggy implementations with lots of tricky edge cases which are not handled well. Beyond this, there's the fact that we're tight on stack space as it is. Opening that space up for potential abuse is just asking for trouble.
I suppose I could come up with a scheme to pull it off anyway, but the stack frame I'm currently using is not friendly for such a thing. I'd have to either make an alternate stack frame for functions which use alloca or simply use another one altogether. Since there are lingering problems with malformed stacks, I don't think it would be wise to make things any more complicated than they already are. At least for now.
That being said, I was looking at the prologue and epilogue code currently in use. There are opportunities for optimization I should look at. The basic idea is that we increment the stack pointer as we restore registers. If local stack is used, there is a final adjustment to correct for that.
There is a four cycle cost for each of those increments. The current code attempts to fold the final increment into the adjustment for the final stack frame adjustment, but only actually does this in rare circumstances.
There was also a pointless "ai r10, 0" instruction emitted in the prologue if no stack usage was used.
That's all easy to fix, but I'm wondering if it's better to not do the folding for small stack sizes. Let's consider the case where one register is saved, and we have two bytes for local usage.
Case 1: Fold increment into stack adjustment
mov *r10, r11 # 4+14+4=22 clocks
ai r10, 4 # 4+14+4=22 clocks
Total: 44 clocks, 6 bytes
Case 2: Preserve increment
mov *r10+, r11 # 4+14+8=26 clocks
inct r10 # 4+10=14 clocks
Total: 40 clocks, 4 bytes
OK, that's a pretty clear win for not folding. I'll get on that.
Alloca is fabled for having buggy implementations with lots of tricky edge cases which are not handled well. Beyond this, there's the fact that we're tight on stack space as it is. Opening that space up for potential abuse is just asking for trouble.
I suppose I could come up with a scheme to pull it off anyway, but the stack frame I'm currently using is not friendly for such a thing. I'd have to either make an alternate stack frame for functions which use alloca or simply use another one altogether. Since there are lingering problems with malformed stacks, I don't think it would be wise to make things any more complicated than they already are. At least for now.
That being said, I was looking at the prologue and epilogue code currently in use. There are opportunities for optimization I should look at. The basic idea is that we increment the stack pointer as we restore registers. If local stack is used, there is a final adjustment to correct for that.
There is a four cycle cost for each of those increments. The current code attempts to fold the final increment into the adjustment for the final stack frame adjustment, but only actually does this in rare circumstances.
There was also a pointless "ai r10, 0" instruction emitted in the prologue if no stack usage was used.
That's all easy to fix, but I'm wondering if it's better to not do the folding for small stack sizes. Let's consider the case where one register is saved, and we have two bytes for local usage.
Case 1: Fold increment into stack adjustment
mov *r10, r11 # 4+14+4=22 clocks
ai r10, 4 # 4+14+4=22 clocks
Total: 44 clocks, 6 bytes
Case 2: Preserve increment
mov *r10+, r11 # 4+14+8=26 clocks
inct r10 # 4+10=14 clocks
Total: 40 clocks, 4 bytes
OK, that's a pretty clear win for not folding. I'll get on that.
Thursday, January 10, 2013
More disappointment. I found the disassembly code in MESS and isolated it in a test program. It seems to work just fine. This really isn't a suprise, since that code's been around for a long time, and disassembly is pretty straightforward for any machine.
So, the question remains: what the heck is going on here? Haven't a clue.
Rather then invest time debugging MESS, I'll just write that off and continue using code and disassembly analysis for my work.
So, the question remains: what the heck is going on here? Haven't a clue.
Rather then invest time debugging MESS, I'll just write that off and continue using code and disassembly analysis for my work.
Tuesday, January 8, 2013
So I was trying to run a test build of an older version of Rush Hour and decided it was time to use the debug facilities of MESS. So far, I've done all my TI work without using any debuggers and I thought it might be nice to cheat a little. Sadly, MESS is acting acting a bit weird.
I set some breakpoints at the start of the cartridge, and they didn't take. After fighting with that for a while, I tried running a simple test cart I was using earlier. That cart eventually hits a "jmp $" instruction and stops. I figured I could at least confirm that the cart is being loaded into the right location.
I think I found the problem. The debugger seems to be misinterpreting the instructions in the disassembly window. Maybe this is causing the breakpoints to be ignored since MESS thinks the address is in the middle of a multiword instruction. Below I've compared the same chunk of code as seen in the debugger window and the objdump disassembly.
From MESS (incorrect):
60C2: 10FF socb *r0, *r12+
60C4: 064A C68B szc r6, @>8bc6(r8)
60C8: 06A0 a r6, r0
60CA: 6768 C0C1 0702 s @>c1c0(r7), @>0207(r1)
From objdump (correct):
60c2: 10ff jmp -2
60c4: 064a dect r10
60c6: c68b mov r11, *r10
60c8: 06a0 6768 bl @>6768
60cc: c0c1 mov r1, r3
So it looks like I should take a look at the MESS sources and see what's going on here. Most likely, there's something misconfigured somewhere.
I set some breakpoints at the start of the cartridge, and they didn't take. After fighting with that for a while, I tried running a simple test cart I was using earlier. That cart eventually hits a "jmp $" instruction and stops. I figured I could at least confirm that the cart is being loaded into the right location.
I think I found the problem. The debugger seems to be misinterpreting the instructions in the disassembly window. Maybe this is causing the breakpoints to be ignored since MESS thinks the address is in the middle of a multiword instruction. Below I've compared the same chunk of code as seen in the debugger window and the objdump disassembly.
From MESS (incorrect):
60C2: 10FF socb *r0, *r12+
60C4: 064A C68B szc r6, @>8bc6(r8)
60C8: 06A0 a r6, r0
60CA: 6768 C0C1 0702 s @>c1c0(r7), @>0207(r1)
From objdump (correct):
60c2: 10ff jmp -2
60c4: 064a dect r10
60c6: c68b mov r11, *r10
60c8: 06a0 6768 bl @>6768
60cc: c0c1 mov r1, r3
So it looks like I should take a look at the MESS sources and see what's going on here. Most likely, there's something misconfigured somewhere.
Subscribe to:
Posts (Atom)