views:

300

answers:

2

I'm porting a small academic OS from TriCore to ARM Cortex (Thumb-2 instruction set). For the scheduler to work, I sometimes need to JUMP directly to another function without modifying the stack nor the link register.

On TriCore (or, rather, on tricore-g++), this wrapper template (for any three-argument-function) works:

template< class A1, class A2, class A3 > 
inline void __attribute__((always_inline)) 
JUMP3( void (*func)( A1, A2, A3), A1 a1, A2 a2, A3 a3 ) {
    typedef void (* __attribute__((interrupt_handler)) Jump3)( A1, A2, A3);
    ( (Jump3)func )( a1, a2, a3 );
}

//example for using the template:
JUMP3( superDispatch, this, me, next );

This would generate the assembler instruction J (a.k.a. JUMP) instead of CALL, leaving the stack and CSAs unchanged when jumping to the (otherwise normal) C++ function superDispatch(SchedulerImplementation* obj, Task::Id from, Task::Id to).

Now I need an equivalent behaviour on ARM Cortex (or, rather, for arm-none-linux-gnueabi-g++), i.e. generate a B (a.k.a. BRANCH) instruction instead of BLX (a.k.a. BRANCH with link and exchange). But there is no interrupt_handler attribute for arm-g++ and I could not find any equivalent attribute.

So I tried to resort to asm volatile and writing the asm code directly:

template< class A1, class A2, class A3 > 
inline void __attribute__((always_inline)) 
JUMP3( void (*func)( A1, A2, A3), A1 a1, A2 a2, A3 a3 ) {
    asm volatile (
                  "mov.w r0, %1;"
                  "mov.w r1, %2;"
                  "mov.w r2, %3;"
                  "b %0;"
                            :
                            : "r"(func), "r"(a1), "r"(a2), "r"(a3)
                            : "r0", "r1", "r2"
                  );
}

So far, so good, in my theory, at least. Thumb-2 requires function arguments to be passed in the registers, i.e. r0..r2 in this case, so it should work.

But then the linker dies with

undefined reference to `r6'

on the closing bracket of the asm statement ... and I don't know what to make of it. OK, I'm not the expert in C++, and the asm syntax is not very straightforward... so has anybody got a hint for me? A hint to the correct __attribute__ for arm-g++ would be one way, a hint to fix the asm code would be another. Another way maybe would be to tell the compiler that a1..a3 should already be in the registers r0..r2 when the asm statement is entered (I looked into that a bit, but did not find any hint).

+1  A: 

The link error is caused by trying to use the branch instruction to jump to a pointer. This generates code like b r6, which fails to link because r6 isn't a symbol. Change the branch instruction to mov pc,%0, and you should get the correct jump.

As I mentioned in the comments, ARM interrupt handlers are declared with an interrupt attribute, but as you found this doesn't effect how they're called. I guess that was a platform-specific trick that happened to do the right thing on TriCore.

You could try declaring variables in specific registers using GCC's extended syntax, register int reg0 asm("r0") = a1; rather than volatile mov instructions. This might allow the compiler to generate better code.

Mike Seymour
A: 

Well, I now figured out what went wrong.

The whole concept of JUMPing directly to another function is moot on ARM Cortex, because TriCore uses a Context Save Area (CSA) to save the whole CPU context everytime you call another function. Think of it as a second, independent stack that grows with each CALL and shrinks with each RET. And each CSA block has constant size.

ARM Cortex, on the other hand, uses a simple standard stack (ok, it knows about a system stack and a thread stack, but that's unimportant here) -- and GCC just saves what it needs for each function, so each frame has a different size. Simply jumping to another function therefore is out of the question, because the stack will be corrupt as soon as the jumped-to function starts saving the non-volatile registers it uses.

And about the linker error with the undefined reference to r6 ... well, I should have read the instruction set documentation more carefully. B is an unconditional branch to an immediate address, BX is the instruction that expects the branch address in a register. I was fooled by the instruction list in the manual where BX was shortly described as "Branch with exchange". I did not want to exchange anything, I wanted a simple jump, so I did not read further.

So, after exchanging B with BX in the asm volatile code, the code compiled. But, as pointed out above, the whole concept cannot work as expected. Maybe someone else can find a use case for that code, I have to resort to classic function calling now ...

maligree