背景
RISCV采用了弱内存模型–RVWMO,在RVWMO模型中,对于WAR,WAW,RAW的约束是相当直观的,非常符合我们的通用思维,:
- WAR: store(new) + load(old),则这笔新的store不能先于老的load发出去。(否则load数据错误)
- WAW: store(new) + store(old),则这笔新的store不能先于老的store发出去。(否则新值被老值覆盖)
- RAW: load(new) + store(old),则这笔新的load需要取回老的store写出去的数据
但还有一条RAR的约束,理解起来有点隐晦:
a and b are loads, x is a byte read by both a and b, there is no store to x between a and b in program order, and a and b return values for x written by different memory operations
翻译下来即是:如果A和B(假设A老于B)为两条访问相同地址的load,则A返回的是老值,B返回的是新值。
这就要求处理器在硬件实现时,特别是乱序处理器,如果有两条地址交叠的load,则新load不能先于老load执行,即RAR约束。
原因
为什么要有这条约束呢?
先说结论:如果软件期望两笔访问相同地址的load能返回不一样的值,允许这两笔load的乱序执行,可能会导致违反软件的期望。因此,RVWMO为了便于软件编程和减少corner场景,便增加了RAR约束。
举个例子:软件人员期望,核0在等到核1对一个地址写新值后,将这个新值读回来,如以下程序。
如果允许hart0的( c )load先于(a)load执行,在以下场景中,(c)load还是可能回读到老值:
1.(a) load由于某种原因被阻塞住,比如和前序指令有数据依赖还没解除
2.(b) 由于(a)没有完成也被堵住不能执行
3.(c) load 将老值load回t1
4.(d) 将新值写到地址X
5.(a) load阻塞解除,将新值load回来,指令提交
6.(b) 预测不跳转,预测成功,指令提交
7.(c) 指令提交,写回老值
因此,如果没有RAR的约束的话,软件就需要在核0的(b)和(c)之间加fence来实现想要的效果了。
引申
如果你仔细观察协议的措辞,你就会发现他非常绕口,为什么不直接写:如果两个load地址交叠,则他们需要保序执行呢?事实上,spec这样写,是为了允许一些特殊情况下,RAR也能乱序执行,但他并不会违反软件人员的期望。其中最典型的场景是fri-rfi场景(案例可参考RISCV手册),但在一般实现中,我们还是不会去为这种场景做特殊化处理,一般都直接约束RAR保序执行。