优化等级引发的RISC-V内联汇编异常问题排查
RISC-V内联汇编CAS代码优化编译错误问题分析
参考的CAS汇编实现(基于RISC-V非特权规范8.2节图8.1)
cas: lr.w t0, (a0) # Load original value. bne t0, a1, fail # Doesn’t match, so fail. sc.w t0, a2, (a0) # Try to update. bnez t0, cas # Retry if store-conditional failed. li a0, 0 # Set return to success. jr ra # Return. fail: li a0, 1 # Set return to failure. jr ra # Return.
含硬编码值的C内联汇编实现
#define BASE 0x8000000000ULL void foo(void) { asm volatile( "1: \n\t" " lr.w t0, 0(%[location]) \n\t" " bne t0, %[expected], 2f \n\t" // mismatch " sc.w t0, %[desired], 0(%[location])\n\t" " bnez t0, 1b \n\t" // retry " li t0, 0 \n\t" // success " j 3f \n\t" "2: \n\t" " li t0, 1 \n\t" // fail "3: \n\t" " sw t0, 0(%[result]) \n\t" : : [result] "p" ((uint32_t*)(BASE+0x013823dc)), [location] "p" ((uint32_t*)(BASE+0x29a5501c)), [expected] "r" (0x581595cf), [desired] "r" (0xace52647) : "t0", "memory" ); }
编译命令
/riscv64-unknown-elf-gcc-8.3.0-2019.08.0-x86_64-linux-centos6/bin/riscv64-unknown-elf-gcc \ -ggdb3 \ -std=gnu99 \ -Wall \ -Wextra \ -Werror \ -Wno-implicit-fallthrough \ -fdata-sections \ -ffunction-sections \ -O0 \ -nostartfiles \ --specs=nano.specs \ -Wl,--gc-sections \ -march=rv64gc \ -mabi=lp64d \ -mcmodel=medany \ -malign-data=natural \ -mriscv-attribute \ foo.c \ start.riscv64.o \ -o foo.elf \ -T./link.riscv64.ld \ ;
问题描述
代码在-O0优化等级下编译正常,但切换至-O1/-Os/-Og/-O2等优化等级时,触发错误:foo.c:99:7: error: invalid 'asm': invalid expression as operand。仅少数同类代码出现此问题,且无法在Godbolt复现。经建议将输入操作数约束从"p"改为"r"后,各优化等级均能正常编译,但对操作数约束的匹配规则存在困惑,需明确:
- 问题成因是什么?
- 是否属于GCC问题?
- 应选用哪种输入操作数约束?
解答
1. 问题成因
"p"约束要求操作数是可直接用作地址的表达式,本质是让GCC生成能直接放在内存操作数位置的常量或寄存器值。但在优化等级≥O1时,GCC会对硬编码地址表达式(uint32_t*)(BASE+0x013823dc)进行更激进的常量折叠和地址分析:
- 目标地址
BASE+0x013823dc属于medany内存模型下的高位地址,虽然合法,但GCC 8.3.0的RISC-V后端在优化时,无法将该常量地址正确适配"p"约束的要求——"p"约束在RISC-V目标中隐含要求地址能被汇编器直接解析为有效内存操作数,而优化过程中GCC对该地址的处理方式(如尝试放入立即数或特殊地址计算)导致与"p"约束的兼容性冲突。 r约束要求操作数放入通用寄存器,GCC会先把硬编码地址加载到寄存器,再在汇编代码中通过寄存器访问内存,避开了直接处理地址表达式的兼容性问题,因此所有优化等级下都能正常编译。
2. 是否属于GCC问题
属于GCC 8.3.0版本RISC-V后端的兼容性问题。该版本后端在处理"p"约束与medany模型下高位地址的组合时,优化阶段的地址处理逻辑存在缺陷。后续版本的GCC(如9.x及以上)已修复这类问题,因此无法在使用较新版本GCC的Godbolt复现错误。
3. 应选用的输入操作数约束
推荐使用"r"约束:
- 你的汇编代码通过
0(%[location])、0(%[result])的形式基于寄存器访问内存,"r"约束正好匹配这种用法——GCC将地址加载到寄存器,汇编代码通过寄存器偏移访问内存。 - 若确实需要使用
"p"约束,需确保地址表达式能被GCC优化阶段正确识别为合法内存地址,或升级到9.x及以上版本的GCC来规避该bug。
补充:操作数约束匹配规则说明
"r":要求操作数存入通用寄存器,汇编代码中通过%[name]直接引用寄存器,适合需要通过寄存器传递值或地址的场景。"p":要求操作数是内存地址表达式,汇编代码中可直接将其作为内存操作数的一部分(如(%[name])),但该约束对地址格式和GCC处理逻辑要求严格,尤其在优化阶段。- 对于RISC-V的
lr.w/sc.w指令,本质需要内存地址,既可以用"p"约束直接传递地址表达式,也可以用"r"约束传递存放在寄存器中的地址,后者兼容性更好,尤其是使用高位地址或旧版本GCC时。
内容的提问来源于stack exchange,提问作者Lance E.T. Compte
相关产品推荐
相关产品推荐

