Is it an anti-pattern to share .macro between multiple global_asm!?

For the context: met unrecognized instruction mnemonic error when naked function uses macro defined in global_asm! · Issue #134960 · rust-lang/rust · GitHub

I'm very new to assembly code, so the answer to this question may be not obvious for me.

Bjorn3 told me assembler state is not guaranteed

There is no guarantee that any assembler state is preserved between inline asm blocks. In fact if we could, we would explicitly prevent you from using this.

does that mean even though the following code compilds with cargo b --target riscv64gc-unknown-none-elf, it's not a good practice to do so

#![no_std]
core::arch::global_asm!(
    r"
.macro LDR rd, rs, off
    ld \rd, \off*8(\rs)
.endm
",
);

core::arch::global_asm!(
    r"
.globl context_switch
context_switch: // omit code around
    LDR s11, a1, 13
",
);

So the only guaranteed way is to put the macro declaration before callsite in the same global_asm statement?

And for the same reason it's also not guaranteed and discouraged to share a macro from global_asm! with inlined asm!?

If you declare a macro, how can you be sure you aren't shadowing a macro the compiler has reserved for itself?

1 Like

Sounds reasonable!

Sharing the trick to reuse .macro pseudo instruction across *asm! in Rust:

macro_rules! LDR {
    () => {
        r"
.macro LDR rd, rs, off
    ld \rd, \off*8(\rs)
.endm
"
    };
}

core::arch::global_asm!(
    LDR!(), // define .macro
    "
.globl context_switch
context_switch: // omit code around
    LDR s11, a1, 13
"
);
As a full example w.r.t conditional compilation using the trick
#[cfg(target_arch = "riscv32")]
macro_rules! STR_LDR {
    () => {
        r"
.ifndef XLENB
.equ XLENB, 4

.macro STR rs2, rs1, off
    sw \rs2, \off*XLENB(\rs1)
.endm
.macro LDR rd, rs, off
    lw \rd, \off*XLENB(\rs)
.endm

.endif"
    };
}

#[cfg(target_arch = "riscv64")]
macro_rules! STR_LDR {
    () => {
        r"
.ifndef XLENB
.equ XLENB, 8

.macro STR rs2, rs1, off
    sd \rs2, \off*XLENB(\rs1)
.endm
.macro LDR rd, rs, off
    ld \rd, \off*XLENB(\rs)
.endm

.endif"
    };
}

#[naked]
/// Switches the context from the current task to the next task.
///
/// # Safety
///
/// This function is unsafe because it directly manipulates the CPU registers.
pub unsafe extern "C" fn context_switch(_current_task: &mut TaskContext, _next_task: &TaskContext) {
    unsafe {
        naked_asm!(
            STR_LDR!(),
            "
            // save old context (callee-saved registers)
            STR     ra, a0, 0
            STR     sp, a0, 1
            STR     s0, a0, 2
            STR     s1, a0, 3
            STR     s2, a0, 4
            STR     s3, a0, 5
            STR     s4, a0, 6
            STR     s5, a0, 7
            STR     s6, a0, 8
            STR     s7, a0, 9
            STR     s8, a0, 10
            STR     s9, a0, 11
            STR     s10, a0, 12
            STR     s11, a0, 13

            // restore new context
            LDR     s11, a1, 13
            LDR     s10, a1, 12
            LDR     s9, a1, 11
            LDR     s8, a1, 10
            LDR     s7, a1, 9
            LDR     s6, a1, 8
            LDR     s5, a1, 7
            LDR     s4, a1, 6
            LDR     s3, a1, 5
            LDR     s2, a1, 4
            LDR     s1, a1, 3
            LDR     s0, a1, 2
            LDR     sp, a1, 1
            LDR     ra, a1, 0

            ret",
        )
    }
}