Dmitry Grinberg 写了篇长文《RISC-V: They Should Have Known Better》,八月十四号挂上 Hacker News,两天里在两个提交下都上了首页。这类文章通常情绪大于信息量,这篇例外:它几乎每一条抱怨都落到具体比特,可以直接拿 objdump 验证。
我对 RISC-V 没有立场,但这篇文章里的几个问题在真实项目里都撞过,值得逐条记下来。
先接受一个前提:没有 ISA 能通吃
文章开头的判断是对的。RISC-V 的支持者喜欢讲它会同时拿下超算、桌面和几毛钱的 MCU,这不可能,对任何 ISA 都不可能。高端核需要的东西和廉价 MCU 核需要的东西直接对立。
廉价核关心的是中断延迟、面积、工具链能不能塞进几 KB 内存,它的工作是给硬件模块打杂——配个 DMA、拧一下寄存器,重活都交给定制 IP。这个场景下,RISC-V 甚至需要 Zicsr 扩展才能算"能规范地处理中断",因为没有 CSR 就没有地方暂存寄存器。MIPS 早年专门留了 $k0/$k1 两个寄存器干这事,RISC-V 靠 mscratch/sscratch,而它们在可选扩展里。缺了它,你就退回到 8051 那种"用奇怪办法做正常事"的状态。
服务端核要的是吞吐。乱序核一次解码八到十条指令,Cache 比核本体还大,代码密度根本不是瓶颈。压缩指令在这种核上是负担而不是收益——Apple 当年做 aarch64 时大量建模测试的结论就是压缩指令在 IPC 和每瓦性能上都是净损失。ARM 的 Thumb 在高性能计算里已经死了,MIPS 如果活到晶体管便宜的时代也会把 microMIPS 砍掉。
压缩指令集:立即数位序像是随手撒的
RVC 的编码质量是全文最实的部分。想给"寄存器加偏移"做一次字节存储,16 位指令能编码的偏移范围是 0 到 3。不是 33,不是 303,是 3。存半字更离谱,只有 0 和 2。存字才有个正常点的范围,0 到 124。
更麻烦的是立即数被拆开、散布在指令各处。作者列了几个真实位序(从左到右是 bit 位置):
C.LWSP 立即数位序: 5 x x x x x 4 3 2 7 6
C.SWSP 立即数位序: 5 4 3 2 7 6
C.LW 立即数位序: 5 4 3 x x x 2 6
C.J 立即数位序: 11 4 9 8 10 6 7 3 2 1 5
C.BEQZ 立即数位序: 8 4 3 x x x 7 6 2 1 5
C.J 那一串基本能过随机数统计检验。同族的两个无条件跳转,编码里几乎没有共同结构。非压缩的 J 型指令也没好到哪去,立即数顺序是 20 10 9 8 7 6 5 4 3 2 1 11 19 18 17 16 15 14 13 12。官方理由是"立即数处于相同位置便于硬件复用",但代价是每写一个反汇编器、模拟器或 JIT,都得先做一次位重排。对比 MIPS 把立即数放 0..15 位、ARM 那套虽然复杂但模式有限,这个设计确实自找麻烦。
作者还指出压缩集里有明显的低垂果实:逻辑左移在 RV32C 里占了 6 位立即数,理由是要兼容 64 位——可 RV64C 和 RV32C 本来就互不兼容,这 6 位里有一半在 32 位模式下纯属浪费。
可选性:一份"选项清单"式的标准
RISC-V 把乘法、除法、用户模式、监督者模式、CSR、压缩指令、额外的压缩指令全部做成可选。规范作者的逻辑是最大化可实现的子集,但每加一个可选项,就把实现空间劈成两组互不兼容的东西。劈得够多次,标准本身就没有约束力了。
最直接的后果是 misa CSR——用来查询 CPU 支持哪些特性的那个寄存器,本身在机器模式,低权限模式根本读不到;就算读得到,硬件也可能把它硬连线成全零。于是代码没法可靠地做特性探测,只能靠设备树或编译期约定。系统定时器同样可选,而且不用 CSR 访问,理由是"省 CSR 编码空间"——总共 4096 个 CSR 编号,x86 都没用掉多少。
异常向量也绕。最终跳到 mtvec/stvec,但 CSR 低两位是模式选择位,于是向量地址必须有对齐约束。这套间接层叠起来,写一个能跑遍各家核心的 bare-metal 异常处理框架,工作量远超直觉。
有人会拿"祖上也这么干"辩护。作者举了 x86(CPUID 能直接查特性,CPUID 指令本身必然存在)和 AArch64(CurrentEL 明明白白告诉你当前特权级)做对照。连虚拟化的理由也站不住:Popek & Goldberg 讨论的是缺少硬件虚拟化支持时的可虚拟化条件,而现代做法是直接内建虚拟化支持。
扩展冲突:同一个字节序列两种含义
这块是真正的硬伤。某些扩展组合在编码上不可共存,比如 Zcmp 和 D。后果是同一个 16 位数值在不同核上有不同解释。作者举的例子:0xA002,在一个实现了压缩浮点的核上是"把双精度浮点寄存器 0 存到栈偏移 0"(C.FSDSP),在实现了 Zcmp 的核上是 cm.push。本来是明确的异常,现在变成不可预测的行为——浮点存储悄悄变成双寄存器搬移或跳转。
x86 背了几十年的兼容包袱都躲开了这个坑,RISC-V 在设计阶段就踩进去了。
缺的指令:都是便宜而且高频的
我不止一次在写网络协议解析时想要"测一位然后跳转",也就是 ARM 的 TBZ/TBNZ。在 RISC-V 上得用 SLLI 加 BGEZ/BLTZ 两条,还要额外的临时寄存器。作者拿树莓派内核 vmlinuz-6.1.0-49-arm64 数了一遍:TBZ/TBNZ 出现 35,393 次。这个量级说明指令不是"锦上添花"。
位域抽取和插入也没有。抽取能用两条移位凑合,插入要造掩码、清位、移位、或回去,三到六条。处理网络包和硬件寄存器时,这类操作遍地都是。
最贵的缺失是寻址模式。x86 有 [ebx + esi*4],ARM 有 [R0, R1, LSL #2],RISC-V 只能先左移、再相加、再访存。数组遍历这种最常见的操作,每条访问多两条指令,编译器还很难全局优化掉。
profile 是第二层标准,也是承认第一层失效
现在官方推 profile(比如 RVA23),把一堆"必选的扩展"打包成清单——某种意义上这是在承认基础规范不足以描述一台真实机器。生态被迫发明第二层标准化来回答"第一版标准里到底哪些部分可以假设存在"。
RISC-V 会不会赢?作者的判断我很认同:它会拿下一部分现在由 8051 占着的角落(有些核可以免费授权,价格优势太大),也会成为 ML 加速器和 DMA 引擎旁边的管家核。但在桌面和高性能计算这类市场,它没有设计上的优势,那里的利润率也付得起更好的核 IP。开放规范不会凭空变出一个好的乱序核——如果真有人设计出来了,也不会免费送。
落到工程上的几条建议
要是你正在评估或者已经上了 RISC-V,这几点我会当检查项:
- 别信
misa做特性探测。用设备树(riscv,isa、riscv,isa-extensions)加编译期-march双保险,运行时探测只当补充。 - 明确绑定 profile。在文档里写死 RVA23 之类的基线,比写"RISC-V 64 位"有意义得多。
- 代码密度要实测,不看宣传。同一份固件分别用 RV32IMC 和 RV32IM 编一遍,
size对比 VMA 段大小,差值常常落在 20% 到 30%。 - 反汇编器别自己写。立即数重排、扩展冲突这些坑,自己实现基本一定会踩到
0xA002那种歧义上。 - 如果目标平台性能敏感,把"有没有
[base + index*scale]这类寻址"当成硬性评估项。差异在数组密集的代码里会直接体现在周期数上。
RISC-V 值得用,前提是清楚它为什么便宜。
浙公网安备 33010602011771号