Horray! All optimizations have been implemented and tested. I've also added the quicker comparisons for -2,-1,1,2.
So the objective now is to test this with a larger program, and make sure the generated code runs properly. Should be fine, though. Once that's complete, I need to make patches and a new build procedure.
By the way, use -Os to optimize for size. Handy for the TI.
Wednesday, November 10, 2010
Tuesday, November 9, 2010
You know, after implementing all the optimizations listed above, I realized something: neg(b1000...) == b1000...
This means I can't use NEG for the comparison test. Poop. I guess I'll have to lose the ~1 clock bonus of NEG. On the up side, that means a LOT fewer peepholes to test (even though it was all written and tested...)
So new plan.
Use ABS for all tests with dead registers, except for A>=0 tests. I can still use INV for that one.
This means I can't use NEG for the comparison test. Poop. I guess I'll have to lose the ~1 clock bonus of NEG. On the up side, that means a LOT fewer peepholes to test (even though it was all written and tested...)
So new plan.
Use ABS for all tests with dead registers, except for A>=0 tests. I can still use INV for that one.
Monday, November 8, 2010
Although replacing MOV with INV or NEG is faster for that single instruction, what is the impact for the overall sequence? Am I just getting wrapped up in all this for no real gains? Time to double check.
I need some shorthand for the compound jumps, so here are the cycle timings for each possible exit from a compund jump:
jlt: 4+(8..10) -> 14 = 14
jeq: 4+(8..10) -> 12+14 = 26
none: 12+12 = 24
Min and max timings for some instructions:
mov A, A -> 4+14 + (1..12)*2 = 20..42
inv A -> 4+10 + (1..12) = 15..26
neg A -> 4+12 + (1..12) = 17..28
abs A -> 4+(12..14) + (1..12) = 17..30
A<0 : mov A, A; jlt : (20..42)+(12..14) = (32..56)
A<=0 : mov A, A; jlt; jeq : (20..42)+(14..26) = (34..68)
A==0 : mov A, A; jeq : (20..42)+(12..14) = (32..56)
A!=0 : mov A, A; jeq : (20..42)+(12..14) = (32..56)
A>0 : mov A, A; jeq : (20..42)+(12..14) = (32..56)
A>=0 : mov A, A; jlt; jeq : (20..42)+(14..26) = (34..68)
Proposed optimizaitons
A<0 : inv A; jgt; jeq : (15..26)+(14..26) = (29..52)
A<=0 : neg A; jgt; jeq : (17..28)+(14..26) = (31..54)
A==0 : neg A; jeq : (17..28)+(12..14) = (29..42)
A!=0 : neg A; jeq : (17..28)+(12..14) = (29..42)
A>0 : neg A; jlt : (17..28)+(12..14) = (29..42)
A>=0 : inv A; jlt : (15..26)+(12..14) = (27..40)
A<0 : abs A; jlt : (17..30)+(12..14) = (29..44)
A<0 : neg A; jgt : (17..28)+(12..14) = (29..42)
I need some shorthand for the compound jumps, so here are the cycle timings for each possible exit from a compund jump:
jlt: 4+(8..10) -> 14 = 14
jeq: 4+(8..10) -> 12+14 = 26
none: 12+12 = 24
Min and max timings for some instructions:
mov A, A -> 4+14 + (1..12)*2 = 20..42
inv A -> 4+10 + (1..12) = 15..26
neg A -> 4+12 + (1..12) = 17..28
abs A -> 4+(12..14) + (1..12) = 17..30
A<0 : mov A, A; jlt : (20..42)+(12..14) = (32..56)
A<=0 : mov A, A; jlt; jeq : (20..42)+(14..26) = (34..68)
A==0 : mov A, A; jeq : (20..42)+(12..14) = (32..56)
A!=0 : mov A, A; jeq : (20..42)+(12..14) = (32..56)
A>0 : mov A, A; jeq : (20..42)+(12..14) = (32..56)
A>=0 : mov A, A; jlt; jeq : (20..42)+(14..26) = (34..68)
Proposed optimizaitons
A<0 : inv A; jgt; jeq : (15..26)+(14..26) = (29..52)
A<=0 : neg A; jgt; jeq : (17..28)+(14..26) = (31..54)
A==0 : neg A; jeq : (17..28)+(12..14) = (29..42)
A!=0 : neg A; jeq : (17..28)+(12..14) = (29..42)
A>0 : neg A; jlt : (17..28)+(12..14) = (29..42)
A>=0 : inv A; jlt : (15..26)+(12..14) = (27..40)
A<0 : abs A; jlt : (17..30)+(12..14) = (29..44)
A<0 : neg A; jgt : (17..28)+(12..14) = (29..42)
Saturday, November 6, 2010
After spellunking for months trying to get REG_DEAD notes into the compiled RTL, it turns out that they are not necessary anymore. Apparently this changes somewhere in the 3.X versions of GCC (I want to say 3.5, but I'm not sure about that. I read about this at work earlier, and I don't remember the details right now. Not really important now.)
I read a lot of posts from the GCC developers, and apparently, I shouldn't need to modify anything beyond the machine-dependant code to achieve everything I'm looking for. This is really good to know, since that should help reduce the time spent researching the GCC front end. Although, I'm kinda glad I did that work now.
So I'm going to implement the optimizations listed in September as peepholes. Should be pretty straightforward, really.
Repeating the optimization list from above:
Baseline:
mov Rx, Rx (14 cycles)
These all assume compared register will be dead
Compare to 2: dect G (10 cycles)
Compare to 1: dec G (10 cycles)
Compare to -1: inc G (10 cycles)
Compare to -2: inct G (10 cycles)
A<0 -> inv A; A>=0 (10 cycles) lt
A<=0 -> neg A; A>=0 (12 cycles) le
A==0 -> neg A; A==0 (12 cycles) eq x
A!=0 -> neg A; A!=0 (12 cycles) ne x
A>0 -> neg A; A<0 (12 cycles) gt
A>=0 -> inv A; A<0 (10 cycles) ge
lt (<)
le (<=)
eq (==)
ne (!=)
gt (>)
ge (>=)
ltu (< unsigned)
leu (<= unsigned)
gtu (> unsigned)
geu (>= unsigned)
I might not use the C pattern though..
Assume instructions are in slow mem, registers are fast
inct r1; inct r2 (4+10+1 + 4+10+1 = 30 cycles) %100
inc r1; inc r2 (4+10+1 + 4+10+1 = 30 cycles) %100
c *r1+, *r2+ (4+14 + 8+4 + 8+4 = 42 cycles) %140
cb *r1+, *r2+ (4+14 + 6+4 + 6+4 = 38 cycles) %126
So this form saves two bytes, but is about a third slower, and is difficult to induce. I think I'll pass on this.
I read a lot of posts from the GCC developers, and apparently, I shouldn't need to modify anything beyond the machine-dependant code to achieve everything I'm looking for. This is really good to know, since that should help reduce the time spent researching the GCC front end. Although, I'm kinda glad I did that work now.
So I'm going to implement the optimizations listed in September as peepholes. Should be pretty straightforward, really.
Repeating the optimization list from above:
Baseline:
mov Rx, Rx (14 cycles)
These all assume compared register will be dead
Compare to 2: dect G (10 cycles)
Compare to 1: dec G (10 cycles)
Compare to -1: inc G (10 cycles)
Compare to -2: inct G (10 cycles)
A<0 -> inv A; A>=0 (10 cycles) lt
A<=0 -> neg A; A>=0 (12 cycles) le
A==0 -> neg A; A==0 (12 cycles) eq x
A!=0 -> neg A; A!=0 (12 cycles) ne x
A>0 -> neg A; A<0 (12 cycles) gt
A>=0 -> inv A; A<0 (10 cycles) ge
lt (<)
le (<=)
eq (==)
ne (!=)
gt (>)
ge (>=)
ltu (< unsigned)
leu (<= unsigned)
gtu (> unsigned)
geu (>= unsigned)
I might not use the C pattern though..
Assume instructions are in slow mem, registers are fast
inct r1; inct r2 (4+10+1 + 4+10+1 = 30 cycles) %100
inc r1; inc r2 (4+10+1 + 4+10+1 = 30 cycles) %100
c *r1+, *r2+ (4+14 + 8+4 + 8+4 = 42 cycles) %140
cb *r1+, *r2+ (4+14 + 6+4 + 6+4 = 38 cycles) %126
So this form saves two bytes, but is about a third slower, and is difficult to induce. I think I'll pass on this.
Tuesday, November 2, 2010
That last frame problem has been solved. I had written that the frame pointer had a role in determining whether R11 should be saved. That was a mistake, one has nothing to do with the other. This was seen when optimization was off because the frame pointer is aways used at this optimization level.
I don't know if this was causing problems yet, but frame_pointer_needed and df_ever_alive() were being factored into the R11 save calculation as well. These are always set for R11 since it's the return pointer, but it only really needs to be saved if the function is not a leaf.
I don't know if this was causing problems yet, but frame_pointer_needed and df_ever_alive() were being factored into the R11 save calculation as well. These are always set for R11 since it's the return pointer, but it only really needs to be saved if the function is not a leaf.
Friday, October 29, 2010
I found another problem with the stack frame stuff. Poop. I noticed with Tursi's test code that the epilogue was popping one too many words. It turns out that the epilogue code assumes that if there is any stack to save, the return pointer will be saved, and so one more word needs to be popped. This has been fixed.
Fixed the mistaken leaf-ness mentioned above. The variable current_function_is_leaf is evaluated late, but the function which drives this, leaf_function_p(), appears to be valid some time earlier.
Uggh, not another problem... When no optimization is called for, the frame pointer is used to store local values. Unfortunately, the current mapping between the frame and stack pointers is wrong, and results in bad addresses being calculated for local values. There is zero offset between stack and frame, and locals are indexed off frame. No provision is made for the saved registers on the bottom of the stack.
Fixed the mistaken leaf-ness mentioned above. The variable current_function_is_leaf is evaluated late, but the function which drives this, leaf_function_p(), appears to be valid some time earlier.
Uggh, not another problem... When no optimization is called for, the frame pointer is used to store local values. Unfortunately, the current mapping between the frame and stack pointers is wrong, and results in bad addresses being calculated for local values. There is zero offset between stack and frame, and locals are indexed off frame. No provision is made for the saved registers on the bottom of the stack.
Thursday, October 28, 2010
I've been working on lifetime calculation for REG_DEAD notes, but I got a message from the AtariAge forums. Tursi was trying to use the compiler, and found some stack layout problems. I got a chance to look at that today. I've found three problems, and fixed two of them.
In one of the prologue forms, the location of the saved registers was mistakenly calculated to be at the top of the stack. This is the only place where that assumption was made.
In the event of a call frame being needed without saved registers, no space was being allocated for the frame registers, The epilogue was fine in this case, and would result in a crash somewhere down the line.
The last problem is that the leaf-ness of a function seems to be calculated after tms9900_starting_frame_offset is called. This means that the frame offset calculation assumes that the link register needs to be saved, and leaves space for it. However, when the prologue is called, we know that the function is a leaf, and no space is saved for the frame, and stack corruption results. I need to find a way to check for leaf-ness earlier in the function construction. Somehow.
In one of the prologue forms, the location of the saved registers was mistakenly calculated to be at the top of the stack. This is the only place where that assumption was made.
In the event of a call frame being needed without saved registers, no space was being allocated for the frame registers, The epilogue was fine in this case, and would result in a crash somewhere down the line.
The last problem is that the leaf-ness of a function seems to be calculated after tms9900_starting_frame_offset is called. This means that the frame offset calculation assumes that the link register needs to be saved, and leaves space for it. However, when the prologue is called, we know that the function is a leaf, and no space is saved for the frame, and stack corruption results. I need to find a way to check for leaf-ness earlier in the function construction. Somehow.
Subscribe to:
Posts (Atom)