1
votes

I am trying to adopt a piece of code, where there is a declaration of a C routine; which mainly consists of an inline assembly code. However when I compile/link it, IN case i call the C-routine from main I get the assembly/linker error

> C:\Documents and Settings\ge\Skrivbord\LED strip\GITHUB code>avr-gcc -g -Os -mmcu=atmega8 -c ws2.c
C:\DOCUME~1\ge\LOKALA~1\Temp/ccVFmJez.s: Assembler messages:
C:\DOCUME~1\ge\LOKALA~1\Temp/ccVFmJez.s:136: Error: symbol `pr_byte' is already defined
C:\DOCUME~1\ge\LOKALA~1\Temp/ccVFmJez.s:146: Error: symbol `one_bit' is already defined
C:\DOCUME~1\ge\LOKALA~1\Temp/ccVFmJez.s:154: Error: symbol `both_low' is already defined
C:\DOCUME~1\ge\LOKALA~1\Temp/ccVFmJez.s:163: Error: symbol `nxt_byte' is already defined
C:\DOCUME~1\ge\LOKALA~1\Temp/ccVFmJez.s:169: Error: symbol `done' is already defined

where as if I comment out the call I do not get that error, code is as follows:

#include <avr/io.h>

#define F_CPU 8000000   // 8MHz


void update_line( const void *values, uint16_t array_size, uint8_t output_bit){


asm volatile(

" LD __tmp_reg__, %a[dataptr]+       \n"     
" LDI %[bits], 8                     \n"     // load no of bits in byte count

"pr_byte:                            \n" 
" LSL __tmp_reg__                    \n"     //  Load next bit into carry flag.
" OUT %[portout], %[upreg]           \n"     // Go high, start of bit-symbol,
" BRCS one_bit                       \n"     //  bit value is in carry flag
" NOP                                \n"
" OUT %[portout], %[downreg]         \n"     // go low for 0-bit-symbol (3clc) (carry low)

" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"
"  rjmp both_low                     \n"     // 

"one_bit:                            \n"     //  

" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"     
" OUT %[portout], %[downreg]         \n"     // go low for the 1-bit-symbol               
"both_low:                           \n"     //  both low; time to initiate next bit 
" SUBI %[bits], 1                    \n"     //  bit countdown for byte
" BREQ nxt_byte                      \n"     // 

" NOP                                 \n"     // 5nop still bits left to process
" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"
" NOP                                 \n"
" rjmp pr_byte                        \n"     // take next bit

// end of previous byte, pick new 
"nxt_byte:                           \n"     // 3used/10
" SBIW %[bytes], 1                   \n"     // byte countdown
" BREQ done                          \n" 
" LD __tmp_reg__, %a[dataptr]+       \n"     // Load next byte
" LDI %[bits], 7                     \n"     // load bit-in-byte count

// fill time before next bit-symbol, none left, kritical ?
"rjmp pr_byte                        \n"     // take next

"done:  NOP \n"   // program should end in low note, som contents will be displayed

: /* no output */
: /* inputs */
[dataptr] "e" (values),                  
[upreg]   "r" (set_obit_high),             
[downreg] "r" (set_obit_low),            
[bytes]   "w" (size),                      
[bits]    "d" (bitcount),                
[portout] "I" (_SFR_IO_ADDR(WS2811_PORT)) 

);
}  /* update_line() */

int main () {

uint8_t LED_rgb;
struct rgb LED_string[48];    // a continuous string of 3 bytes(rgb)
int LED_len = 48; 

//update_line( LED_string, 48*3, 1);  //<*************

} /*end main*/

`


so the interresting thing is there seem to have been generated labels for the "update_line" routine first when the routine is declared, and then ALSO when the routine is called ! so in some way it seems that the whole routine becomes "inline" (in the sense that its code is placed at the position of call itself). Im a bit at loss as both what is happening and as to what can be done (there seem to be possible to generate new labels using %= / but still why should the code be multiplied)

tnx Georg

2
I suspect that if you were to use -S to generate the assembler output (foo.S), you would see the problem. The S file will likely contain both the function definition for update_line, as well as the inlined version included into main(), both of which define the same symbols. You might be able to avoid the problem using "local labels" (or whatever implementation your assembler has), but %= might actually be better (not sure what happens if main calls update_line twice). However, let me take a minute to pitch NOT using inline asm (see this).David Wohlferd
Canonical Q&A for how to deal with the duplicate label problem. Not a duplicate because this question is asking why the compiler can duplicate your asm statement in the first place.Peter Cordes

2 Answers

2
votes

GCC provides a mechanism to counteract this in the form of %=.


According to GCC's documentation (under Special format strings):

‘%=’

Outputs a number that is unique to each instance of the asm statement in the entire compilation. This option is useful when creating local labels and referring to them multiple times in a single template that generates multiple assembler instructions.

Simply appending this to the names of your labels (including in instructions that reference the labels) should fix the duplication problem.

The compiler does this by (as described in the afforementioned quote) associating a number with each instance of 'pasted' assembly code and replacing %= with that number.


(I can verify this works because I've just had the exact same problem on a much smaller scale, which is how I ended up here in the first place.)

1
votes

void update_line(args){ body } is not just a declaration. It's a global definition that the compiler can't omit, even if it decides to inline it into every caller in this compilation unit (this .c file).

So the compiler has to emit a stand-alone definition of the function.

It also has to copy it verbatim into all places where it decides to inline the function body into callers. This is what it means to inline a function. If you don't want that, use __attribute__((noinline)). gcc doesn't "read" your asm, so it doesn't realize that there's a lot of code. I thinks of it as just a single instruction (that behaves as described by the constraints), so it looks like a small function that it should inline everywhere it's used.

If you'd used static void update_line, the compiler would know that this definition wasn't visible from outside the compilation unit, and leave out the stand-alone definition.

But it could still inline into two or more callers, so you should use %= as part of your label names. (Or numbered labels, and refer to them with 1f or 1b for forward/backward).


Or better, only use inline asm for the I/O instructions. Write the branching logic in C so you don't need a giant block of asm, and the compiler can optimize that code into the caller.

Or if you really want to write the whole function in asm (so you can use NOPs for delays I guess?), you could just write it as a stand-alone function that you call from C.