|
From: | Bruce D. Lightner |
Subject: | [avr-gcc-list] "Mixed" Assembly Listings (and Degenerate I/O Code Examples) |
Date: | Fri, 06 Oct 2006 12:23:37 -0700 |
User-agent: | Thunderbird 1.5.0.7 (Windows/20060909) |
Tom Deutschman wrote:
For your reference, here are links to PDFs of assembler listing and map file comparisons from your example source when compiled with avr-gcc 3.4.6 and 4.1.1 using your specified compiler options (plus those required to generate the assembly listing and map files.)This has been reported as a "bug" in the GNU assembler in the past, but in fact it is caused by "pilot error". It seems that the GNU assembler needs "stabs" debug information in order to extract source code information for the purposes of inserting it "in-line" with the machine code in the "avr-gcc" assembly listing. Here's what I use in my "Makefiles"... %.o : %.c $(CC) $(CFLAGS) $(D) -c -gstabs -Wa,-ahlmsd=$*.lst -o $@ $< Note the "-gstabs". The sample "Makfile" shipped with WinAVR was altered at some point to re-define "DEBUG" to "dwarf-2" instead of "stabs". This "broke" the in-line source code feature in the resultant assembly listing files. This is the likely the source of the confusion that followed. The ability to see the AVR machine code "in context" with the source code is an *essential* feature of any compiler "tool kit" used for embedded programming. Sometimes compilers (and especially "avr-gcc") do some pretty stupid things! An example of why more people should be "watching" the "avr-gcc" code generator are these three simple C routines which test a bit in one of the AVR microcontroller I/O ports. Because of the AVR instruction set's clever "sbic" and "sbis" instructions, the machine code for tests like these should be short...and very fast. However, with modern versions of "avr-gcc" (i.e., 3.x.x and 4.x.x) "your mileage may vary from that shown on the window sticker"! #include <avr/io.h> #define J1708_IN_PORT PORTD #define J1708_IN_DDR DDRD #define J1708_IN_PIN PIND #define J1708_IN_BIT 3 int test1(void) { unsigned char bit = (J1708_IN_PIN & (1 << J1708_IN_BIT)) ? 1 : 0; return bit; } int test2(void) { if (!(J1708_IN_PIN & (1 << J1708_IN_BIT))) return 0; return 1; } int test3(void) { if (!(J1708_IN_PIN & (1 << J1708_IN_BIT))) return 1; return 0; } ...compiled with avr-gcc 3.4.6: C:/WinAVR_20060421/bin/avr-gcc -mmcu=atmega16 -Os \ -mcall-prologues -Wall -c \ -gstabs -Wa,-ahlmsd=x.lst -o test.o test.c ...gives us horrible code for all except "test3()": **** int test1(void) **** { **** unsigned char bit = (J1708_IN_PIN & (1 << J1708_IN_BIT)) ? 1 : 0; test1: in r24,48-0x20 clr r25 ldi r18,3 1: lsr r25 ror r24 dec r18 brne 1b andi r24,lo8(1) **** return bit; **** } clr r25 ret **** **** int test2(void) { **** if (!(J1708_IN_PIN & (1 << J1708_IN_BIT))) return 0; test2: in r24,48-0x20 clr r25 movw r18,r24 andi r18,lo8(8) andi r19,hi8(8) sbrc r24,3 rjmp .L3 movw r24,r18 ret .L3: **** return 1; ldi r24,lo8(1) ldi r25,hi8(1) **** } ret **** int test3(void) { **** if (!(J1708_IN_PIN & (1 << J1708_IN_BIT))) return 1; test3: sbic 48-0x20,3 rjmp .L5 ldi r24,lo8(1) ldi r25,hi8(1) ret .L5: **** return 0; ldi r24,lo8(0) ldi r25,hi8(0) **** } ret The bit-shift "loop" code generated for "test1()" is really "special". :-) (Yes, that's 5 clocks per bit shift!) I was lucky that my application only referenced bit 3 in the I/O port. If the hardware had used bit 6, the routine would take twice as long to execute...over 33 clocks for an I/O bit test that should have taken just a couple of clocks! Note that the only difference between "test2()" and "test3()" is the return value (i.e., 0 vs. 1). Experiments show that returning "0" is what triggers the "bad behavior". This bad example takes 6 AVR instructions to do the work of one "sbic" instruction. This kind of "optimized" machine code makes me miss the code generator logic in "avr-gcc" v2.95...and the v2.95 I/O macros like "sbi()", "cbi()", "bit_is_set()" and "bit_is_clear()" which always did the right thing! (Eric/Joerg, please don't "flame" me...again!) :-) So, if you are doing any time-critical checking of I/O bits using "avr-gcc", beware! Regular inspection of a "mixed listings" from the GNU assembler is essential! In many real-time embedded applications, code this bad means that your application is "broken". BTW: Here's my "cheap fix" for the "test2()" example: #define _BYTE(ch) ({ \ uint8_t t; \ asm volatile ( \ "ldi %0, %1" \ : "=d" (t) \ : "M" ((uint8_t)(ch)) \ ); \ t; \ }) int test2(void) { if (!(J1708_IN_PIN & (1 << J1708_IN_BIT))) return _BYTE(0); return 1; } ...which eliminate the degenerative behavior when trying to "return 0" as part of an I/O port bit check. So, does anyone know if "avr-gcc 4.x.x" just as "special"? Maybe an automatic code-generation "regression test" for this is needed, assuming that will get the problem fixed in a future "avr-gcc" release? This particular code generation bug has been in "avr-gcc" for literally years now! I'd be happy to provide some test code, if the idea of "regression testing" of the "avr-gcc" code generator is even a "concept". (Yes, I know it is a "hard problem", but look what "slips through the cracks"!) Or,maybe it's time to bring back *real* in-line assembly macros for "sbi()", "cbi()", "bit_is_set()" and "bit_is_clear()"! The compiler clearly doesn't get it right all the time when it comes to critical I/O code, and when it's wrong, it is *really* bad! Best regards, Bruce -- Bruce D. Lightner Lightner Engineering La Jolla, California Voice: +1-858-551-4011 FAX: +1-858-551-0777 Email: address@hidden URL: http://www.lightner.net/lightner/bruce/ |
[Prev in Thread] | Current Thread | [Next in Thread] |