美文网首页
Rocket Core : RAS(Return Address

Rocket Core : RAS(Return Address

作者: gs要加油呀 | 来源:发表于2020-01-09 18:00 被阅读0次

    Rocket Chip 代码注释已上传至github,持续更新中。

    Return Address Stack

    RAS(Return Address Stack), 即返回地址预测栈,该模块针对函数调用进行优化,在取指令前端维护了一个硬件实现的返回地址预测栈

    1. 当执行函数调用(call)时,将函数返回地址(Return Address)入栈
    2. 当函数返回(return)时,直接从栈中弹出地址进行取指令

    RAS更新相关的指令

    call和ret都是RISC-V中的伪指令,查阅riscv-spec中伪指令的定义,可以看到call和return指令主要与JAL、JALR指令相关。

    call与return伪指令

    但是,从上表可以看出,call和ret指令在关于JALR这条指令上有所重叠,同样是JALR指令,有些是call指令,有些是ret指令,为了区分两者,必须仔细查阅一下手册。


    Unconditional Jumps

    JALR指令的语义是:

    1. 跳转到base+offset地址
    2. 将返回/链接地址写入dest寄存器
    3. 若返回/链接地址无意义,则dest寄存器置为x0(针对ret指令)

    The standard software calling convention uses:

    • x1 as the return address register
    • x5 as an alternate link register

    根据指令集手册中的说明,我们可以进而知道标准的软件调用过程中:

    1. 函数调用时,我们希望写入RAS栈中的地址在dest字段(即rd字段),约定使用x1与x5寄存器;
    2. 相应地,当函数返回时,返回地址通常也在x1与x5寄存器, 位于base字段(即rs1字段),rd字段通常为x0。

    了解了上述细节,可以查看Rocket Core中的实现来验证我们的理解(显然,我已经验证过了hh)

    // 见 Frontend.scala
    val rviJump = rviBits(6,0) === Instructions.JAL.value.asUInt()(6,0)
    val rviJALR = rviBits(6,0) === Instructions.JALR.value.asUInt()(6,0)
    val rviReturn = rviJALR && !rviBits(7) && BitPat("b00?01") === rviBits(19,15)
    val rviCall = (rviJALR || rviJump) && rviBits(7)
    

    Rocket Core中的实现细节

    了解了相关指令之后,我们下面关注RAS的具体实现细节,riscv-spec中还明确了RAS的push/pop条件与规范。如下表所示

    RAS Push/Pop规范

    link: 代表JAL/JALR指令的rs1或rd字段是否使用x1或x5寄存器

    表中前三项为常规操作

    • 无关指令: 空操作
    • Call指令: rd寄存器内容入栈(push)
    • ret指令: 出栈(pop)并预测跳转

    下面两项比较特别,下面简单说明一下

    1. 支持协程实现

    一般情况下,函数调用过程中CallReturn指令是配对出现的。(不考虑程序抛出异常)
    但是协程与一般函数调用过程不同
    (TODO:还没弄清楚orz)

    2. macro-op fusion

    TODO

    Chisel代码实现

    Rocket Chip中用面向对象的方式将RAS封装成了一个类(而不是RTL代码中常见的module的写法),定义了相应的push、pop、peek等接口,为了方便理解,我绘制了一个示意图。


    RAS示意图

    基于示意图,代码还是比较好理解的。

    // Return Address stack
    //   主要针对函数调用进行优化:
    //      - Call相关指令入栈, 记录返回地址
    //      - Return相关指令出栈, 根据记录的返回地址预测跳转
    class RAS(nras: Int) {
      // 函数调用时入栈顶
      def push(addr: UInt): Unit = {
        when (count < nras) { count := count + 1 }
        val nextPos = Mux(Bool(isPow2(nras)) || pos < nras-1, pos+1, UInt(0))
        stack(nextPos) := addr
        pos := nextPos
      }
      // 读出栈顶元素
      def peek: UInt = stack(pos)
      // 函数返回出栈
      def pop(): Unit = when (!isEmpty) {
        count := count - 1
        pos := Mux(Bool(isPow2(nras)) || pos > 0, pos-1, UInt(nras-1))
      }
      def clear(): Unit = count := UInt(0)
      def isEmpty: Bool = count === UInt(0)
    
      // Bug!!! 这里count和pos应该用RegInit,复位初始化为0
      // 当前栈中返回地址个数
      private val count = Reg(UInt(width = log2Up(nras+1)))
      // 栈顶指针
      private val pos = Reg(UInt(width = log2Up(nras)))
      // 栈本体 (:-P)
      private val stack = Reg(Vec(nras, UInt()))
    }
    

    一些注意事项

    1. 未实现RAS pop then push 操作

    截取RAS实例化后的代码

      if (btbParams.nRAS > 0) {
        val ras = new RAS(btbParams.nRAS)
        //
        val doPeek = (idxHit & cfiType.map(_ === CFIType.ret).asUInt).orR
        io.ras_head.valid := !ras.isEmpty
        io.ras_head.bits := ras.peek
        when (!ras.isEmpty && doPeek) {
          io.resp.bits.target := ras.peek
        }
        when (io.ras_update.valid) {
          when (io.ras_update.bits.cfiType === CFIType.call) {
            ras.push(io.ras_update.bits.returnAddr)
          }.elsewhen (io.ras_update.bits.cfiType === CFIType.ret) {
            ras.pop()
          }
        }
      }
    

    可以发现,这里并没有实现指令集手册中的pop then push的操作。

    [未完待续,不定期补充]

    参考资料

    相关文章

      网友评论

          本文标题:Rocket Core : RAS(Return Address

          本文链接:https://www.haomeiwen.com/subject/sgxuactx.html