int main() { for(;;) { P1_6 = 0; P1_6 = 1; } }
Tried LX51 link with: (SMALL / SMALL)
TO "Clock" REGFILE (.\Clock.ORC) RESERVE (C:0x0800-C:0x7FFF)
Result:
Build target 'Target 1' compiling main.c... linking... Program Size: data=9.0 xdata=0 const=0 code=21 creating hex file from "Clock"... "Clock" - 0 Error(s), 0 Warning(s).
HEX output: :030000000280007B :10800000787FE4F6D8FD75810702800CC296D2967F :0280100080FAF4 :00000001FF
Target device: AT89LP214. Has 2KB of code space, which ends at 0x07FF. Everything I try sticks the code at 0x0800 or higher.
I'm going to be sad if I have to link code manually every time I compile.
I've tried not reserving any code space at all; linker sticks it at 0x0800. I've tried setting the target device to AT89LP214 and letting keil figure things out. Sticks it at 0x0800 with the LX51 linker, doesn't compile with BL51. I've tried changing memory models, code rom size models, same result. 0x0800 with LX51, doesn't compile with BL51. Tried checking and unchecking "Use On-Chip ROM (0x0-0x7FF)".
Using uVision V4.60.6.10, Toolchain PK51 9.51, C51 v9.51, A51 V8.02b, BL51 6.22, LX51 V4.64.0.0
For example, on max "optimizations":
void transmit(char bar) { } void calls_transmit(long arg) { transmit((arg & 0xFF000000) >> 24); transmit((arg & 0xFF0000) >> 16); transmit((arg & 0xFF00) >> 8); transmit((arg & 0xFF)); } int main() { long arg = 0xAABBCCDD; calls_transmit(arg); return 0; }
compiles to:
; FUNCTION _transmit (BEGIN) 8F00 R MOV bar,R7 22 RET ; FUNCTION _transmit (END) ; FUNCTION _calls_transmit (BEGIN) 8F00 R MOV arg+03H,R7 8E00 R MOV arg+02H,R6 8D00 R MOV arg+01H,R5 8C00 R MOV arg,R4 E4 CLR A FF MOV R7,A FE MOV R6,A FD MOV R5,A E500 R MOV A,arg 7818 MOV R0,#018H 1100 E ACALL ?C?ULSHR 1100 R ACALL _transmit E4 CLR A FF MOV R7,A FE MOV R6,A E500 R MOV A,arg+01H FD MOV R5,A E4 CLR A FC MOV R4,A 7810 MOV R0,#010H 1100 E ACALL ?C?SLSHR 1100 R ACALL _transmit E4 CLR A FF MOV R7,A E500 R MOV A,arg+02H FE MOV R6,A E4 CLR A FD MOV R5,A FC MOV R4,A 7808 MOV R0,#08H 1100 E ACALL ?C?SLSHR 1100 R ACALL _transmit E500 R MOV A,arg+03H FF MOV R7,A 0100 R AJMP _transmit ; FUNCTION _calls_transmit (END) ; FUNCTION main (BEGIN) ;---- Variable 'arg' assigned to Register 'R4/R5/R6/R7' ---- 7FDD MOV R7,#0DDH 7ECC MOV R6,#0CCH 7DBB MOV R5,#0BBH 7CAA MOV R4,#0AAH 1100 R ACALL _calls_transmit E4 CLR A FE MOV R6,A FF MOV R7,A ?C0003: 22 RET ; FUNCTION main (END) Code size: 70 bytes
It **Could be**, assuming we're naïve and still do the things the compiler likes to do:
ASSEMBLY LISTING OF GENERATED OBJECT CODE ; FUNCTION _transmit (BEGIN) ; ** this MOV is actually unnecessary. 8F00 R MOV bar,R7 ; ** With function inlining, this RET is also unnecessary. ; Because this function does nothing. 22 RET ; FUNCTION _transmit (END) ; FUNCTION _calls_transmit (BEGIN) 8F00 R MOV arg+03H,R7 8E00 R MOV arg+02H,R6 8D00 R MOV arg+01H,R5 8C00 R MOV arg,R4 MOV R7, arg ACALL _transmit MOV R7, arg + 01H ACALL _transmit MOV R7, arg + 02H ACALL _transmit MOV R7, arg + 03H AJMP _transmit ; FUNCTION _calls_transmit (END) ; FUNCTION main (BEGIN) ;---- Variable 'arg' assigned to Register 'R4/R5/R6/R7' ---- 7FDD MOV R7,#0DDH 7ECC MOV R6,#0CCH 7DBB MOV R5,#0BBH 7CAA MOV R4,#0AAH 0008 1100 R ACALL _calls_transmit ** Removes 3 unnecessary assigns to A, R6, and R7. ?C0003: 22 RET ; FUNCTION main (END) 38 bytes total.
Or simply:
RET
Another example:
line level source char getRand() { return 5; } char callsGetRand() { return getRand(); } int main() { return callsGetRand(); } ASSEMBLY LISTING OF GENERATED OBJECT CODE ; FUNCTION getRand (BEGIN) 7F05 MOV R7,#05H0002 ?C0001: 22 RET ; FUNCTION getRand (END) ; FUNCTION callsGetRand (BEGIN) 1100 R ACALL getRand0002 ?C0002: 22 RET ; FUNCTION callsGetRand (END) ; FUNCTION main (BEGIN) 1100 R ACALL callsGetRand EF MOV A,R7 33 RLC A 95E0 SUBB A,ACC FE MOV R6,A ?C0003: 22 RET ; FUNCTION main (END)
Lack of inlining here produces an extra few bytes as well. And I have no idea what the compiler's doing inside of main.
line level source 3 void foo() 4 { 22 RET 5 1 } 7 int main() 8 { 9 1 char a; 10 1 11 1 for(a = 0; a != 10; a++) 12 1 foo(); E4 CLR A FF MOV R7,A ?C0002: 1100 R ACALL foo 0F INC R7 BF0AFA CJNE R7,#0AH,?C0002 14 1 for(a = 10; a; a--) ; This is optimal! 15 1 foo(); ?C0003: 7F0A MOV R7,#0AH ?C0005: 1100 R ACALL foo DFFC DJNZ R7,?C0005 16 1 17 1 a = 10; 18 1 while(a != 0) 19 1 { 20 2 a--; 21 2 foo(); 22 2 } ; This is *not* optimal. Unless you switch source lines 20 and 21 around, then it is. ?C0006: 7F0A MOV R7,#0AH ?C0008: EF MOV A,R7 6005 JZ ?C0010 1F DEC R7 1100 R ACALL foo 80F8 SJMP ?C0008 22 RET 23 1 }
Here, a lack of any sort of source code level analysis yields less than optimal code. The first case, for(a = 0; a != 10; a++) can be rewritten as for(a = 10; a; a--) because a is not used in the body of the loop.
The second case is optimal, thanks to 8051's DJNZ instruction. But, the other 2 cases should be optimized to that, if possible.
The third case is a bit harder for the compiler, but if it had tried switchin a--; and foo() around, it would have found the optimal code.
As far as compile times go, we're not doing #include <windows.h>, which adds 10 million lines of code to the translation unit... There's plenty of power available.
P.S: The biggest thing that makes me sad though, isn't the suboptimal code generation (I can live with that.). It's the number of clicks you have to go through to add another file to your project. Right now its, "New file", "Save as", type name, right click folder, "add file(s) to folder", navigate to where I saved file, repeat 2x for .c and .h file. It *should* be, right click -> add new file. rename it. repeat for .c file.