首页 > 其他分享 >计组学习06——RISC-V Functions

计组学习06——RISC-V Functions

时间:2022-12-18 15:33:54浏览次数:44  
标签:Functions s0 06 计组 sp t0 指令 ra addi

有点发烧,唔,不过还是坚持学下来了,新冠让人静心.

计组学习——RISC-V Functions

Loading Sign Extension

假如内存里的字节是这样:

0b1111 1110(-2)

当我们使用load byte指令时:众所周知,我们需要进行符号拓展

我们此时的序列 0b XXXX XXXX XXXX XXXX XXXX XXXX 1111 1110

就会变成:0b1111 1111 1111 1111 1111 1111 1111 1110

可以发现,进行符号拓展之后,获得的数字不变,依旧是-2

举例:假如s0内的地址为0x00008011

  • lb t0,0(s0) -> loading 0b00010001

    0b0000 0000 0000 0000 0000 0000 0001 0001

  • lb t0,1(s0)-> loading 0b10000000

    0b1111 1111 1111 1111 1111 1111 1000 0000

如果我实际上就想要一个无符号数怎么办呢?我该怎么进行拓展呢?

  • 不进行符号拓展,只要添加前导0就可以了!

  • 用上述例子距离:lbu t0,1(s0)-> unsigned loading 0b10000000

    0b0000 0000 0000 0000 0000 0000 1000 0000

Pseudo-Instructions 伪指令

伪指令只是更容易懂的指令,所有伪指令都可以拆解成为基础的语句,但是有时使用基础语句对于程序员来说有些麻烦。

同时,为了适配多种芯片,伪指令的概念也是很有必要的。

基础的指令就是硬件能够执行的指令,能在物理层面运行的指令。

  • move

    mv dst,reg1

    将一个寄存器里的值写入到另一个寄存器

    等同于 addi dst,reg1 0

  • load immediate(li)

    li dst, imm

    把一个32位的数字加载到dst里,但事实上我们没有访问内存

    等同于,先addi,在左移lui

    lui是将一个20位的数左移12位,比如lui x5,0x12135

    这是因为addi内存在一些限制,我们并不能加一个32位的数,稍后会提到

  • load address(la)

    la dst, label

    将label标签内的地址加载到dst里

    本质是我们把label的地址转化为物理上的地址(用label相对于pc的地址加上pc的绝对地址),

    之后算出来之后放到dst里

    等同于,auipc dst, <offset to label>

    auipc是讲一个数字imm左移十二位之后加上一个pc的偏移值

    pc指针是program controller,指向了目前运行的指令的地址

    同样的这里的imm只能是20位的数

    auipc是唯一一条能让我们知道当前PC在哪的指令

  • No Operation(nop) 空指令

    nop

    等同于,add x0,x0,0

就连jump这样的指令也都是伪指令,伪指令真的非常有用!

C to RISC-V Practice

  • Fast String Copy

    char *p, *q;
    while( (*q++ = *p++)!= '\0' );
    

虽然代码很简洁,但是我们最好写下每一步!这样方便进行转换!养成好习惯

p->s0,q->s1;
t0=*p;
*q=t0;
p=p+1;
q=q+1;
if *p==0,go to Exit
go to Loop

汇编代码:

Loop: lb    t0,0(s0)
      sb    t0,0(s1)
      addi  s0,s0,1
      addi  s1,s1,1
      beq   t0,x0,Exit
      j Loop
Exit:

可以发现我们在load byte的时候,获取了一个字节,而前面的24位是进行符号拓展的。

但是这不重要!因为我们在sb操作的时候,忽略了前面24位,所以完全没有影响!

  • 可以发现我们在上述过程中,一次循环执行了六条指令,但是我们可以简化!
Loop: lb    t0,0(s0)
      sb    t0,0(s1)
      addi  s0,s0,1
      addi  s1,s1,1
      bne   t0,x0,Loop
Exit:

这样在一次循环里只运行了五次指令!

汇编语言程序(重点!)

调用程序的六个步骤:

  1. 将参数放入到函数可以访问的位置
  2. 将C语言程序拆分为流程
  3. 考虑堆栈空间,给程序一些内存!
  4. 程序按照指令运行
  5. 在将值返回到某些位置之后,记住“clean up”
  6. 结束调用,返回调用前的指令

重点在于:什么时候调用什么寄存器!

  • 步骤1和步骤5:我们应该把参数和返回值放在哪?
    • 因为寄存器比内存快得多,所以把这些值都放在寄存器里
      • a0-a7:八个参数寄存器(argument registers),用来传递参数
      • a0-a1:两个参数寄存器,也被用于传递返回值
    • 如果需要多余的空间!请使用的内存!
    • 参数的顺序很重要!
      • 因为在返回值的时候,我们已经不再需要参数了,所以需要覆盖掉原本的参数

例子: c语言代码:

int add(int a,int b){
    return a+b;
}
void main(void){
    a=3;
    b=a+1;
    a=add(a,b);
}

汇编语言代码:

main:
	addi a0,x0,3
    addi a1,a0,1
    jal ra,add   #jump and link将执行的下一条指令地址(其实就是+4字节,因为riscv每条指令占有4个字节)				    存入ra,并且跳转到add
add:
	add a0,a0,a1
    jr ra        #这里不使用j是因为j跳转的是标签,而我们把下一条指令的地址存到了ra里。同时,ra也叫x1,专				   门用来储存函数的返回地址
  • 步骤2和步骤6:我们如何进行流程控制?

还是上述例子,我们可以看到,jal ra,add我们把下一条指令的地址存入ra(其实就是jal这一条指令的地址+4),之后我们在add函数里jr ra 但是我们都知道!,jr本身是一个伪指令,那么它的本质指令是什么呢?

jr ra =jalr x0,ra,0 我们把ra寄存器的地址加上偏移值(立即数0),那么就是ra的地址,之后我们把这个新地址储存到x0中,这并不会改变x0,x0永远不会改变!

变量的本地存储

本地存储的本质就是sp指针,sp指向栈空间的底部,拓展肯定是向下拓展的,拓展4个字节相当于sp-4

addi sp,sp,-4
sw   t0,0(sp)
#将t0存入栈里

使用寄存器的规范

例如,如下的c++程序

int sumSquare(int x,int y){
    return mult(x,y)+y;
}
  • 我们需要储存什么?
    • 因为调用mult会覆盖掉ra,所以我们应该先存下来
    • 而a0和a1本来存储x和y,如果调用mult,那么我们a1里面y的值就会被x覆盖掉!

调用规范!

  • calleR:在调用别人的函数
  • calleE:被调用的函数

Saved Registers(CalleE Saved)

  • 这些寄存器在CalleE调用前后保持不变
    • 如果calleE使用了他们,他们必须在返回之前重新储存值
    • 这意味着把旧的值存下来(存到栈里),调用寄存器,然后重新把旧的值读入寄存器里
  • s0-s11(saved registers)
  • sp(stack pointer)

Volatile Registers(CalleR Saved)

  • 这些寄存器可以被CalleE自由的改变
    • 如果calleR需要他们,他必须在程序调用之前把值存储起来
  • t0-t6(temporary registers)
  • a0-a7(return address and arguments)
  • ra(return address)

综上所述,寄存器就分为两种:

  • Caller saved
  • CalleE saved

存储register

当然是栈了!

image

那么...回到我们最开始的c++程序:

int sumSquare(int x,int y){
    return mult(x,y)+y;
}

可以把它转换为汇编程序:

sumSquare:
	addi  sp,sp,-8    #在栈里开辟两个空间
    sw    ra,4(sp)    #存储ra
    sw    a1,0(sp)    #存储y的值
    add   a1,a0,x0    #把a1的值改变为x(因为我们调用的是mult(x,x))
    jal   mult        #跳转执行mult
    lw    a1,0(sp)    #执行之后,重新加载a1(其实就是让a1=y)
    add   a0,a0,a1    #计算mul(x,y)+ y
    lw    ra,4(sp)    #加载回ra
    addi  sp,sp,8     #栈指针回溯
    jr    ra          #结束函数调用
mult:
	...               #执行x*x的功能

函数的基本结构:

Prologue
    func_label:
		addi sp,sp,-framsize
        sw ra,<framsize-4>(sp)
        #store other callee saved registers,such as:s0-s11
        #save other registers if needed,such as:a0-a7,t0-t6
Body (call other functions...)
    ...
Epilogue
     #restore other regs if needed
     #restore other callee saved regosisters
     lw ra,<framsize-4>(sp)
     addi sp.sp.framsize
     jr ra

image

标签:Functions,s0,06,计组,sp,t0,指令,ra,addi
From: https://www.cnblogs.com/ZzTzZ/p/16990436.html

相关文章

  • [攻防世界][Web]ics-06
    打开靶机对应的url,展开是一个网站的样子,其实啥也么有所有tab都点一遍,发现只有报表中心有内容,url为http://61.147.171.105:49797/index.php?id=1猜测这里是题眼,使用burp......
  • [LeetCode]006-Z字形变换
    >>>传送门题目将一个给定字符串s根据给定的行数numRows,以从上往下、从左到右进行 Z字形排列。比如输入字符串为"PAYPALISHIRING" 行数为3时,排列如下:PA......
  • 计组学习05——Introduction to Assembly Language
    感觉可算到了真正计组重要的部分(毕竟我是因为挖操作系统的时候感觉计组知识严重不足来补的计组。为了看清楚问题和ppt,倍速减慢截取字幕消失的那几帧....然后还有一些解......
  • 【《硬件架构的艺术》读书笔记】06 流水线的艺术(3)
    6.9流水线冒险冒险会干扰流水线并阻止下一条指令在目标时钟周期内的执行。冒险会降低流水线在理想情况下所能带来的速度提升。冒险分类:1、结构冒险:资源冲突导致硬件无......
  • Next.js 开发商 Vercel 正式推出 Edge Functions
    Next.js开发商Vercel正式推出EdgeFunctions来源:OSCHINA编辑: 白开水不加糖2022-12-1707:49:24 0Next.js框架背后的开发商Vercel 宣布推出Edg......
  • 前端开发系列106-小程序篇之衣橱商城首页功能实现
    title:'前端开发系列106-小程序篇之衣橱商城首页功能实现'tags:categories:[]date:2018-12-2118:30:39本文简单说明衣橱商城首页界面的实现过程,包括对商城首页的......
  • 【《硬件架构的艺术》读书笔记】06 流水线的艺术(2)
    6.6DLX指令集的实现这节开始将指令集相关内容,没学过相关知识,看不太懂,就快速浏览一下好了。DLX指令集包括五个部分:1、指令获取(IF)IR<=MEM[PC]NPC<=PC+4从存储器......
  • P2466 [SDOI2006]Sue的小球
    我们注意到,这道题要求小球最大价值和,并且即使权值变为负的也要算上。因此,状态转移时只要维护所有小球损失的价值“最小即可。这是一道典型的区间dp,因此我们可以设值状态......
  • day36_0654.最大二叉树
    0654.最大二叉树该题部分没思路部分有思路但不会写代码因为自己写不出完整代码所以笔记就看卡哥解答过程吧详细通俗易懂我这里简单记录一下我都卡在什么地......
  • day37_0617.合并二叉树
    0617.合并二叉树classSolution{public:TreeNode*mergeTrees(TreeNode*root1,TreeNode*root2){intval1=0,val2=0;if(root1!=NUL......