Search code examples
cassemblygccinline-assemblyriscv

Cannot specify the operands when using inline asm outside a function


My code is

unsigned long user_stack_pointer;

__asm__(
    ".global exception_handling_entry\n"
    "exception_handling_entry:\n"
    "add %0, sp, x0\n"
    : "=r" (user_stack_pointer)
);

gcc shows a error ')' before ':' token when using riscv64-unknown-elf-gcc test.c -o test. But it works well when I put the inline asm into a function such as

unsigned long user_stack_pointer;

int main() {
    __asm__(
    ".global exception_handling_entry\n"
    "exception_handling_entry:\n"
    "addi %0, sp, 0\n"
    : "=r" (user_stack_pointer)
    );

    return 0;
}

This confuses me a lot.

I can not find a good solution for it.


Solution

  • At global scope, the code only runs if something jumps inside the asm statement itself. There's no way for the path of execution in C terms to enter and leave the asm statement, so there's nowhere for the compiler to put code that sets up input operands or that stores output operands to associated C variables. If you want that to happen, you have to write code for it yourself, exactly like writing a function in a separate .s file. (Use .text inside your global-scope asm statement in case the compiler left the current section something else.)

    e.g. sd sp, user_stack_pointer as a pseudo-instruction, or auipc with the %hi part of the address and use %lo(user_stack_pointer) as the offset in a store instruction. Look at compiler-generated asm to see how it accesses global vars (https://godbolt.org/); I'm just going from memory and that might not be quite right for RV64; I might be remembering MIPS.

    Or if you want to have the compiler do the .global and label for you, use __attribute__((naked)). You still have to write the entire function body with Basic asm statements (no operands); it's not officially supported to use Extended asm (with operands) in naked functions: https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Function-Attributes.html - but it's only at most a warning not a compile-time error, so the compiler won't stop you from relying on happens-to-work code.

    Also, the compiler doesn't emit a ret at the end of a naked function, so can't let execution come out the bottom of your asm statement anyway, making an output operand useless. You need to ret or jump somewhere.


    It would actually be undefined behaviour if execution came out the bottom of the asm statement after jumping into the middle (e.g. like this exception handler rather than execution starting with the C __asm__ statement itself) no matter where it is, even inside a function. As the GCC manual documents, asm statements may not perform jumps into other asm statements, only to the listed GotoLabels. GCC’s optimizers do not know about other jumps; therefore they cannot take account of them when deciding how to optimize.

    If that happens to work, you got lucky. The code for the "=r"(global_var) operand could rely on some registers set up by instructions the compiler put ahead of the asm statement, which wouldn't have executed if you jump into the middle of it.