7

I'm working with a proprietary MCU that has a built-in library in metal (mask ROM). The compiler I'm using is clang, which uses GCC-like inline ASM. The issue I'm running into, is calling the library since the library does not have a consistent calling convention. While I found a solution, I've found that in some cases the compiler will make optimizations that clobber registers immediately before the call, I think there is just something wrong with how I'm doing things. Here is the code I'm using:

int EchoByte()
{
    register int asmHex __asm__ ("R1") = Hex;
    asm volatile("//Assert Input to R1 for MASKROM_EchoByte"
            :
            :"r"(asmHex)
            :"%R1");
    ((volatile void (*)(void))(MASKROM_EchoByte))(); //MASKROM_EchoByte is a 16-bit integer with the memory location of the function
}

Now this has the obvious problem that while the variable "asmHex" is asserted to register R1, the actual call does not use it and therefore the compiler "doesn't know" that R1 is reserved at the time of the call. I used the following code to eliminate this case:

int EchoByte()
{
    register int asmHex __asm__ ("R1") = Hex;
    asm volatile("//Assert Input to R1 for MASKROM_EchoByte"
            :
            :"r"(asmHex)
            :"%R1");
    ((volatile void (*)(void))(MASKROM_EchoByte))();
    asm volatile("//Assert Input to R1 for MASKROM_EchoByte"
            :
            :"r"(asmHex)
            :"%R1");
}

This seems really ugly to me, and like there should be a better way. Also I'm worried that the compiler may do some nonsense in between, since the call itself has no indication that it needs the asmHex variable. Unfortunately, ((volatile void (*)(int))(MASKROM_EchoByte))(asmHex) does not work as it will follow the C-convention, which puts arguments into R2+ (R1 is reserved for scratching)

Note that changing the Mask ROM library is unfortunately impossible, and there are too many frequently used routines to recreate them all in C/C++.

Cheers, and thanks.

EDIT: I should note that while I could call the function in the ASM block, the compiler has an optimization for functions that are call-less, and by calling in assembly it looks like there's no call. I could go this route if there is some way of indicating that the inline ASM contains a function call, but otherwise the return address will likely get clobbered. I haven't been able to find a way to do this in any case.

Sam Cristall
  • 4,328
  • 17
  • 29
  • 1
    I think I'd fold the call into the asm block to be sure that everything was set up appropriately. I'm not completely following why that doesn't work for you. – JasonD Jan 03 '13 at 20:16
  • The main issue stems from the MCU lacking Push/Pop. Functions without calls are optimized to not do any stack manipulation, and folding the call into the asm block doesn't look like a call, meaning in some cases the callee will clobber the return address. Is there a way of indicating that an asm block contains a call? – Sam Cristall Jan 03 '13 at 20:18
  • 3
    can you not write a complete c-callable function in asssembly, which would call the build-in library? – AShelly Jan 03 '13 at 20:23
  • 1
    @S.C.M. Can you add the return address register to the clobber list? – JasonD Jan 03 '13 at 20:29
  • I can, I was hoping I didn't have to, for other unfortunate reasons, but it's looking like that's probably the best way, thanks. – Sam Cristall Jan 03 '13 at 20:40
  • If you declare the C-callable function `static __inline__ ...` in a header, then gcc usually manages to never actually create it (and always embed the code at the callsite). – FrankH. Jan 04 '13 at 13:57
  • 1
    Simple: The `asm()` instruction (nothing, really; but the compiler just leaves that alone) finishes, and then you do your funky call. If you want to do some weird calling convention, you have to do it yourself inside the `asm()` – vonbrand Jan 23 '13 at 00:16

1 Answers1

3

Per the comments above:

The most conventional answer is that you should implement a stub function in assembly (in a .s file) that simply performs the wacky call for you. In ARM, this would look something like

// void EchoByte(int hex);

_EchoByte:
    push {lr}
    mov  r1, r0       // move our first parameter into r1
    bl   _MASKROM_EchoByte
    pop  pc

Implement one of these stubs per mask-ROM routine, and you're done.

What's that? You have 500 mask-ROM routines and don't want to cut-and-paste so much code? Then add a level of indirection:

// typedef void MASKROM_Routine(int r1, ...);
// void GeneralPurposeStub(MASKROM_Routine *f, int arg, ...);

_GeneralPurposeStub:
    bx   r0

Call this stub by using the syntax GeneralPurposeStub(&MASKROM_EchoByte, hex). It'll work for any mask-ROM entry point that expects a parameter in r1. Any really wacky entry points will still need their own hand-coded assembly stubs.

But if you really, really, really must do this via inline assembly in a C function, then (as @JasonD pointed out) all you need to do is add the link register lr to the clobber list.

void EchoByte(int hex)
{
    register int r1 asm("r1") = hex;
    asm volatile(
        "bl  _MASKROM_EchoByte"
        :
        : "r"(r1)
        : "r1", "lr"   // Compare the codegen with and without this "lr"!
    );
}
Quuxplusone
  • 23,928
  • 8
  • 94
  • 159