32

Is it possible to get the value of a #defined integer symbol to be inserted verbatim into a string literal that is part of an assembly section in GCC (AVR Studio)?

I want the "LEDS" to be replaced by 48 within the string literal inside the asm() block below.

#define LEDS 48 //I only want ONE mention of this number in the source
int x = LEDS;   //I'm using the value directly too

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, LEDS       \n\t" //<-- substitution needed here
...
}

But I want the compiler/assembler (after the preprocessor has done it's job) to see this...

#define LEDS 48 //I only want ONE mention of this number in the source
int x = LEDS;   //I'm using the value directly too

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, 48         \n\t" //<-- substitution needed here
...
}

So far I have tried all the macro tricks I can think of (#stringification, arg substitution and even #including files with various combinations of values and double quotes and whatnot).

I'm not at all familiar with the magic of inlining AVR assembly code into AVR Studio's GCC compiler.

I'm trying to avoid having multiple occurrences of the "48" literal in my source, if the preprocessor can perform this substitution for me that would be great.

Edit: This is for a microcontroller firmware project - and just to make life interesting, there is almost no spare room for new code to be added.

  • Take a look at [Placing a preprocessor directive inside a string literal](http://stackoverflow.com/q/14721007/2305521) – fpg1503 Nov 14 '16 at 14:43
  • 2
    Why doesn't stringification and string-concatenation work? – Cody Gray Nov 14 '16 at 14:49
  • @CodyGray, stringification doesn't "evaluate" what you pass in, it simply takes the characters within the argument and dumps them straight out vrbatim unless you use an extra macro to substitute the desired characters before stringification. –  Nov 14 '16 at 15:07
  • 1
    How is this not a duplicate? I thought the double-macro technique suggested by @CodyGray and in PSkocik's answer was well-known, so surely it's been asked before. (This is now a good canonical Q&A for this issue, though, so less-clear questions with less-good answers could be marked as duplicates of this.) – Peter Cordes Nov 14 '16 at 22:26

3 Answers3

47

I think it's good to have a stringifying macro in your utils header:

#define STR_IMPL_(x) #x      //stringify argument
#define STR(x) STR_IMPL_(x)  //indirection to expand argument macros

Then you can keep the macro numerical and stringify it on the spot:

#define LEDS 48 
int x = LEDS;   

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, "STR(LEDS)"       \n\t"
...
}

The above preprocesses to:

int x = 48;

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, ""48""       \n\t"
...
}

which relies on the fact that adjacent string literals get concatenated.

PSkocik
  • 52,186
  • 6
  • 79
  • 122
  • 4
    This is a better version of my answer. Upvoted. – 2501 Nov 14 '16 at 14:49
  • Ahh, yes that seems to do the trick. I think I had something similar to this but my #defines were bass ackwards. Splendid. –  Nov 14 '16 at 15:01
13

You can avoid the stringification macro mess if you use a constraint:

#define LEDS 48

void DrawFrame()
{
    asm volatile(
    "ldi R18, %[leds]"
    : : [leds] "M" (LEDS) : "r18");
}
Jester
  • 52,795
  • 4
  • 67
  • 108
  • 1
    Interesting. This page looks relevant: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html. In this instance I'll stick with the nasty macros since it is more easily understood by C programmers (sort of!). –  Nov 14 '16 at 15:37
  • 2
    @Wossname: How are you going to use "basic" asm (without constraints) without stepping on the compiler's toes (i.e. clobbering registers without telling it)? See the [inline assembly tag wiki](http://stackoverflow.com/tags/inline-assembly/info) for more info. "Extended" asm is the normal way to use it, and AFAIK the only safe way to do more than use an instruction that doesn't affect registers (e.g. a memory fence or something). – Peter Cordes Nov 14 '16 at 22:21
  • @PeterCordes, I'm only using ASM in order to get the necessary instructions done in a critical time frame. As soon as the ASM returns back to the C code all the memory used in the ASM is re-initialised. The assembler output indicates that in this isn't going to be an issue. But you're right to raise this point, if I were to make a habit of using AVR assembly (not likely) then I would certainly look into the correct way to do things. As it stands, the worst thing that will happen is that my xmas tree will stop flashing for a couple of seconds. :) –  Nov 15 '16 at 07:56
  • @Wossname: I said registers, not memory. "Basic" asm statements are assumed not to clobber any registers, so your code will easily break if you compile with optimization enabled. (And if you're not doing that... why the hell not, especially if space is tight?) If you really refuse to do this properly, then use extended asm without any input/output operands, but that declares a clobber on every reg you touch. `asm("..." : : : "r18", "r19", "r20" );` – Peter Cordes Nov 15 '16 at 08:02
  • I'll look into it in a bit and update my question accordingly. –  Nov 15 '16 at 08:21
  • @PeterCordes, I've read through the GNU docs for this kind of syntax but it seems to omit some specific real world cases (eg how ASM may access the address of an array that was declared in C code and how to protect it with constraints). Do you know of any detailed tutorials relating to mixing C and ASM within the GCC toolchain? I've put a request in on the "Documentation Beta" pages in case someone wants to write one. –  Nov 15 '16 at 11:21
  • @Wossname: yes, that's why I told you to look at http://stackoverflow.com/tags/inline-assembly/info, because I added the good links to that tag wiki already. It's a good idea to use an operand to tell the compiler you're accessing a symbol, but you can just write the symbol name in the asm directly like you were going to do with Basic asm. – Peter Cordes Nov 15 '16 at 19:10
12

You need two auxiliary macros for this to work. Then you can take advantage of automatic string concatenation:

#define STR(x) #x
#define EXPAND(x) STR(x)

#define LEDS 48
int x = LEDS;

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, " EXPAND(LEDS) "       \n\t"
...
}

The reason for using two macros is that the first alone won't expand the parameter passed in.

If you just did this:

printf("LEDS = " STR(LEDS) "\n");

It would expand to this:

printf("LEDS = " "LEDS" "\n");

The EXPAND macro allows the parameter passed in to be substituted as well.

So then this:

printf("LEDS = " EXPAND(LEDS) "\n");

Would expand to this:

printf("LEDS = " "48" "\n");
dbush
  • 162,826
  • 18
  • 167
  • 209