In general, it is possible to use inline assembler statements within C macros, for example

#define toscreencode(C) (__AX__ = (C),   \
                         asm("cmp #$60"),\
                         asm("bcc L1"),  \
                         asm("and #$df"),\
                         asm("bne L2"),  \
                         asm("L1:"),     \
                         asm("and #$3f"),\
                         asm("L2:"),     \

This works fine as long as the macro is called at most once per module. However, if the macro is called more than once, the compiler throws errors for the two labels ("Error: ASM label 'L1' is already defined") since the named labels are now used twice since the code is duplicated every time the macros is used.

Unfortunately, it is not possible to use a .scope / .endscope with the inline assembler command. I also tried cheap local labels ("@L1" instead of "L1"), but the error persists.

Unnamed labels (":+", ":") are not accepted by the inline assembler.

It is also not possible to hack the branch command by replacing it with two recalculated bytes, because the .byte command does not work with the inline assembler.

Any suggestions on how to make the macro usable more than once in a module?


Since the inline assembler of cc65 doesn't accept anonymous labels (from my other answer), another approach is to provide a unique suffix to the labels, which can be applied by the macro. The stringizing operator of the C preprocessor, and the fact that C string literals written consecutively are automatically concatenated, might make this more convenient:

#define toscreencode(C, _suffix) \
                        (__AX__ = (C),   \
                         asm("cmp #$60"),\
                         asm("bcc @L1_" #_suffix),  \
                         asm("and #$df"),\
                         asm("bne @L2_" #_suffix),  \
                         asm("@L1_" #_suffix ":"),     \
                         asm("and #$3f"),\
                         asm("@L2_" #_suffix ":"),     \

Call as, for example:

x = toscreencode(y, A);

…which will use labels @L1_A and @L2_A.

It turns out that unique label suffixes can also be generated automatically, though this does explore yet more of the outer reaches of the C preprocessor - relying on argument prescan which is probably an even more obscure detail than the stringizing operator, and the automatically incrementing __LINE__ macro. We actually have to use nested macro calls to make this work:

#define stringify(_x) #_x
#define superstringify(_z) stringify(_z)
#define suffix superstringify(__LINE__)
#define toscreencode(C) (__AX__ = (C),   \
                         asm("cmp #$60"),\
                         asm("bcc @L1_" suffix),  \
                         asm("and #$df"),\
                         asm("bne @L2_" suffix),  \
                         asm("@L1_" suffix ":"),     \
                         asm("and #$3f"),\
                         asm("@L2_" suffix ":"),     \

Make sure every invocation of the macro is on a different line.

Unfortunately, although the above code should work, it appears that cc65 doesn't implement the C preprocessor accurately enough to support it. Fortunately, it turns out that there is a simpler and less subtle approach since asm() supports a printf-like syntax:

#define toscreencode(C) (__AX__ = (C),   \
                         asm("cmp #$60"),\
                         asm("bcc @L1_%s", __LINE__),  \
                         asm("and #$df"),\
                         asm("bne @L2_%s", __LINE__),  \
                         asm("@L1_%s:", __LINE__),     \
                         asm("and #$3f"),\
                         asm("@L2_%s:", __LINE__),     \

Again, every invocation of this macro must be on a different line to deconflict the labels.

  • Works, thanks a lot! Any idea how the suffix could be generated automatically? – Peter B. Nov 24 '20 at 11:46
  • @PeterB. Actually, yes. We have to jump through some more hoops as this is really stretching the preprocessor's capabilities, but we can derive the suffix from the macro invocation's line number. I'll add to this answer once I figure out the details. – Chromatix Nov 24 '20 at 14:01
  • Nice answer +10 :) – Raffzahn Nov 24 '20 at 14:21
  • I just tried out the second version, but I keep getting the errors "Error: ASM label '@L1___LINE__' is already defined" and "ASM label '@L2___LINE__' is already defined". It seems that __LINE__ is not expanded into a line number. I did a copy paste from above code, did I forget something? – Peter B. Nov 24 '20 at 22:04
  • New solution, one script, works, I verified it in the generated assembly code and tested the resulting program: #define toscreencode(C) \ (AX = (C), \ asm("cmp #$60"),\ asm("bcc @L1_%s",LINE), \ asm("and #$df"),\ asm("bne @L2_%s",LINE), \ asm("@L1_%s:",LINE), \ asm("and #$3f"),\ asm("@L2_%s:",LINE), \ AX) – Peter B. Nov 24 '20 at 22:53

You can use anonymous labels in ca65. The equivalent to your example code using these, but without the C wrapping, would be:

  CMP #$60
  BCC :+
  AND #$DF
  BNE :++
: AND #$3F

You can also use :- to refer to an anonymous label preceding the current position. Increasing the number of + or - symbols increases the number of anonymous labels to skip over.

Another possible workaround, to use in emergencies only, is to directly code the number of bytes between the opcode of the branch instruction and that of the target:

  CMP #$60
  BCC *+6
  AND #$DF
  BNE *+4
  AND #$3F

Note that the actual assembled code will have relative offsets of +4 and +2 respectively, because the 6502 takes the offset from the opcode of the instruction following the branch, but the assembler's * pseudo-variable refers to the instruction currently being assembled.

  • Thanks for the suggestion, but as you have already stated in your other answer (I also mentioned in the question), anonymous labels (or unnamed labels as they are called in the ca65 manual) are not accepted in asm("") statements. – Peter B. Nov 24 '20 at 9:57
  • 1
    @PeterB. Yes, I noticed that detail in the question after writing this answer, so I wrote another one to take account of it. – Chromatix Nov 24 '20 at 10:43
  • Symbol generation seems like a common requirement in any macro assembler. DEC's Macro-11 had provision for this; in .MACRO FOO ARG,?LAB, by virtue of the question mark, the LAB argument will evaluate to a unique local symbol if (as is intended) the call supplies no actual value. – another-dave Nov 25 '20 at 3:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.