This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

What does it mean to say "ARM Cortex-M processors are entirey C programmable"?

Hi,

Being new to the fascinating ARM world, I am going through various documentation and reading material.

I read that the ARM Cortex-M processors (or I think it applies to all the Cortex processors) are entirely C programmable. This means that assembly code is not required for start-up code and for ISRs.

I am curious to find out what prohibits the earlier ARM processors (or even other architectures) to write the start-up code in C? Are these any specific instructions? One which I found was the WFI (Wait For Interrupt) which can't have an equivalent in C. But that is not a start-up or ISR instruction.

Thanks for your support !

Gopal

Parents
  • I am curious to find out what prohibits the earlier ARM processors (or even other architectures) to write the start-up code in C?

    The C language is stack-based so a C compiler assumes there is a valid stack to use when compiling C source code. The ARM v7-M architecture implemented by Cortex-M devices defines the first entry in the vector table to be the Initial Stack pointer value which the hardware copies automatically out of reset into the SP (Stack Pointer); thus, out of reset, the start-up code can be written in C because a valid stack is available.

    Earlier ARM processors like the ARM7 mentioned above, or Cortex-A and Cortex-R devices for the matter, do not have an initial stack value defined out of reset, so the start-up code cannot be written in C.

Reply
  • I am curious to find out what prohibits the earlier ARM processors (or even other architectures) to write the start-up code in C?

    The C language is stack-based so a C compiler assumes there is a valid stack to use when compiling C source code. The ARM v7-M architecture implemented by Cortex-M devices defines the first entry in the vector table to be the Initial Stack pointer value which the hardware copies automatically out of reset into the SP (Stack Pointer); thus, out of reset, the start-up code can be written in C because a valid stack is available.

    Earlier ARM processors like the ARM7 mentioned above, or Cortex-A and Cortex-R devices for the matter, do not have an initial stack value defined out of reset, so the start-up code cannot be written in C.

Children
  • Thanks for the useful examples and information .

    One question: the stack array is declared as "char". I think C compiler could allocated this in non-word aligned addresses. And for AAPCS requirement you might possibly want to make the stack aligned to double word boundaries. So would it be better to declare it as a double word array?

       uint64_t stack[64]; // 512 bytes

    or use some other attribute to force double word data alignment?

    regards,

    Joseph

    (I didn't have enough coffee today so I might have miss something )

  • You're correct, I forgot the alignment attribute in this "example" (e.g. alignas), though it's probably better not to take the example too serious anyway since, as I mentioned it's rather brittle.  Code that requires optimization to be enabled and crashes if it's not would typically be frowned upon I think.

    I'm no fan of the mandatory dword stack alignment.  When targeting cortex-m3 processors where dword alignment has absolutely no benefit I don't want to waste stack space.  If I ever move my code to a processor that does care, I'll recompile.  The alignment assumption is, if I'm not mistaken, indicated in an attribute in the compiled object so if someone accidently tries to link together code with incompatible assumptions it should give a linker error.

  • In theory you can sometimes get away with initializing SP with something like... (yes, those are C++11 style attributes )

    extern void init();
    extern int main();
    
    [[ noreturn ]]
    extern void exit(int);
    
    [[ noreturn ]]
    [[ gnu::noinline ]]
    void _start2()
    {
      init();
      exit( main() );
    }
    
    char stack[512] alignas(8);
    
    [[ noreturn ]]
    void _start()
    {
      asm( "mov sp, %0" :: "r" (stack + sizeof(stack)) );
      _start2();
    }
    
    

    relying on the tail-call being optimized, and clang (git) indeed produces working code from this depending on options, with -O1 -fomit-frame-pointer -fno-exceptions seemingly being the magic ingredients required.  But gcc (4.9.1) keeps insisting on "saving" r3 and lr on the stack, even though it knows the function is noreturn and in fact doesn't generate any corresponding code to "restore" them again.  Putting [[gnu::naked]] on _start() convinces gcc to omit this prologue-of-doom, resulting in functioning code, but this attribute makes clang very unhappy.

    Gcc's stubbornness appears to be a side-effect of excessive precaution around a volatile asm statement, since fetching SP in a similar way does work, e.g. to provide this very useful "add integer" system call:

    struct HandlerFrame {
      u32 r0, r1, r2, r3, r12, lr;
      u32 pc, xpsr;
    };
    
    [[ gnu::noinline ]]
    void do_sys_add( HandlerFrame *frame )
    {
      frame->r0 += frame->r1;
    }
    
    void syscall_handler()
    {
      HandlerFrame *frame;
      asm( "mov %0, sp" : "=r" (frame) );
      do_sys_add( frame );
    }
    
    

    which needs only -O1 for clang and -O2 for gcc to compile correctly.

    Of course, this hackery is brittle as hell and it's not exactly plain C/C++ anymore either; writing some glue in assembly is much saner. Writing ARM assembly is quite pleasant anyhow, we're not talking about x86 here (*shudder*).

    Update: edited to add alignment attribute to stack, which was missing as Joseph Yiu pointed out in the comments.