loongarch32r 内联汇编的一个注意点
前言
前置知识:“CSRWR 指令将通用寄存器 rd 中的旧值写入到指定 CSR 中,同时将指定CSR的旧值更新到通用寄存器 rd 中。“
LoongArch 的调用规则:
| 寄存器 | 别名 | 用途 |
|---|---|---|
| $r4 | a0 | 第 1 个参数 |
| $r5 | a1 | 第 2 个参数 |
| $r6 | a2 | 第 3 个参数 |
| … | … | … |
我在初始化 csr_eentry 的时候,打算将 trap_entry 这个函数地址保存到 csr_eentry,将ecfg 也更新。之后打印出日志:
1 | |
编译出来的汇编是这样的:
1 | |
可以看到,csrwr 操作了 r5 以后,r5 的值就会被改变。而 myprintf 函数的参数是: r4、r5,前者放一个字符串,后者就是改变了值的变量 entry_addr。
这也导致我打印了错的 eentry:
1 | |
解决
1 | |
这样就能解决了吗?不行,编译出来的代码没变:
1 | |
因为编译器认为,我的内联汇编的两个输入寄存器不会变化,因此 uint32_t saved_entry = entry_addr 代码没有必要,于是什么都没有发生。
很显然,编译没有识别出 csrwr 的内涵,造成了错误的优化。
那么怎么修改才能达到我的代码目的?
“+r”(val) — 读写 ✓ 正确
1 | |
编译器知道这个寄存器既被读取又被修改,于是正确地理解了 myprintf("[TRAP] Initialized, eentry=0x%08x\n", saved_entry) 的语义,确保 saved_entry 不会被修改,于是保护了它:
1 | |
可以看到,编译器用 r12 去做 csrwr,这样 r5 就不会改变,被保护起来了。
总结
完整的逻辑链
- “+r”(val) 告诉编译器:这个操作数会被修改
- 编译器分析数据流,发现:
- entry_addr 在 csr_write 之后还要被 myprintf 使用
- 但 csrwr 会修改操作数寄存器
- 编译器决策:
- 不能直接用 entry_addr 的寄存器(r5)执行 csrwr
- 必须先复制到临时寄存器(r12)
- 用 r12 执行 csrwr(r12 被破坏无所谓)
- r5 保持不变,myprintf 可以正常使用
生成的代码正是这个逻辑:
1 | |
如果没有 “+r”
- “r”(val) 告诉编译器:这只是输入,不会被修改
- 编译器错误地认为:
- csrwr 不会改变操作数
- 可以直接用 r5 执行 csrwr
- 结果:
- csrwr r5, 0xc 执行后,r5 变成 CSR 旧值
- myprintf 拿到的是错误的值
一句话总结:”+r” 让编译器知道寄存器会被修改,从而在需要时保护后续还要使用的变量。
loongarch32r 内联汇编的一个注意点
http://blog.luliang.online/2026/01/07/loongarch中的一个内联汇编/