\documentclass[全部作业]{subfiles} \input{mysubpreamble} \setcounter{chapter}{1} \begin{document} \chapter{指令:计算机的语言} \begin{enumerate} \questionandanswer[2.3]{ [ 5 ]<2.2,2.3>对于以下C语句,请编写相应的RISC-V汇编代码。假设变量f、g、h、i和j分别分配给寄存器x5、x6、x7、x28和x29。假设数组A和B的基地址分别在寄存器x10和x11中。 \mint{C}|B[8] = A[i-j];| }{} {\kaishu % 这个minted环境放到自定义的命令里好像一直会报错 这里假设数组A和B中的元素都是64位的,即“双字”,即8个字节。 \begin{minted}{asm} sub x30, x28, x29 // i-j,从A开始的双字偏移 slli x30, x30, 3 // (i-j)*8, 从A开始的字节偏移 add x30, x30, x10 // A[i-j]的地址 ld x31, 0(x30) // 加载A[i-j] sd x31, 64(x11) // 放到B[8],8是双字偏移,转化成字节偏移就是64 \end{minted} % 由于minted用了verbatim,而verbatim的文档中有这样一句话: % You cannot use the verbatim environment inside user defined commands; % 可以看到在自己定义的环境里无法使用minted了。 \begin{verification} 使用命令 \mintinline{shell}|riscv64-unknown-linux-gnu-gcc -S main.c -o main.s| 编译,左边是C代码,右边是汇编代码。 \begin{minipage}{0.5\linewidth} \begin{minted}{C} ... long long *A, *B, i,j; B[8] = A[i - j]; ... \end{minted} \end{minipage} \begin{minipage}{0.5\linewidth} \begin{minted}{asm} ... ld a4,-24(s0) // 加载i ld a5,-32(s0) // 加载j sub a5,a4,a5 slli a5,a5,3 ld a4,-40(s0) // 加载A add a4,a4,a5 ld a5,-48(s0) // 加载B addi a5,a5,64 ld a4,0(a4) sd a4,0(a5) ... \end{minted} \end{minipage} 比较后可以发现基本差不多,但是最后一步赋值时这里先用了addi偏移64位,再存储,为什么不是直接64(a5)一步存储到从a5偏移64个字节的位置呢? \end{verification} } \questionandanswer[2.4]{ [ 10 ]<2.2,2.3>对于以下 RISC-V汇编指令,相应的C语句是什么?假设变量f、g、h、i和j分别分配给寄存器x5、x6、x7、x28和x29。假设数组A和B的基地址分别在寄存器x10和x11中。}{} \begin{minted}[fontfamily=tt]{asm} slli x30, x5, 3 // x30 = f*8 add x30, x10, x30 // x30 = &A[f] slli x31, x6, 3 // x31 = g*8 add x31, x11, x31 // x31 = &B[g] ld x5, 0(x30) // f = A[f] addi x12, x30, 8 // x12 = (&f + 1) = (原始的f)&A[f + 1] ld x30, 0(x12) // x30 = A[f + 1] add x30, x30, x5 // x30 = f + A[f + 1] = (原始的f)A[f] + A[f + 1] sd x30, 0(x31) // B[g] = A[f] + A[f + 1] \end{minted} {\kaishu 注释已写在上方,因此此汇编相应的C语句如下: \mint{C}|B[g] = A[f] + A[f + 1];| } \questionandanswer[2.10]{ 假设寄存器x5和x6分别保存值0x8000000000000000和0xD000000000000000。 }{} \questionandanswer[2.10.1]{ [ 5 ]<2.4>以下汇编代码中x30的值是多少? \mint{asm}|add x30, x5, x6| }{ 由于后面全是0,只需要考虑最高4位二进制。x5为$8_{16}$即$1000_{2}$,x6为$\mathrm{D}_{16}$即$1101_{2}$,要注意最高位为1代表负数,由于负数已按照补码方式表示,所以可以直接相加。这里两个数都是负数,相加后为$1\ 0101_{2}$,进位忽略,即$0101_{2}=5_{16}$,此时x30的值为 $$ 0 \mathrm{x} 5000000000000000 $$ } \questionandanswer[2.10.2]{ [ 5 ]<2.4> x30中的结果是否为预期结果,或者是否溢出? }{ 两个负数相加,结果的最高位为0,表示正数,不是预期结果,即产生了溢出。 } \questionandanswer[2.10.3]{ [ 5 ]<2.4>对于上面指定的寄存器x5和x6的内容,以下汇编代码中x30的值是多少? \mint{asm}|sub x30, x5, x6| }{ 还是只考虑最高4位二进制,最高位为1代表负数,直接相减不方便,可以先按照补码方式转换为相反数再相加,按照补码方式$1101_{2}$的相反数为$0011$,因此$1000_{2}-1101_{2}=1000_{2}+0011_{2}=1011_{2}$,即$\mathrm{B}_{16}$。因此x30的值为 $$ 0 \mathrm{x} \mathrm{B}000000000000000 $$ } \questionandanswer[2.10.4]{ [ 5 ]<2.4> x30中的结果是否为预期结果,或者是否溢出? }{ 两个负数相减,结果仍为负数,最高位是1,为预期结果,无溢出。 } \questionandanswer[2.10.5]{ [ 5 ]<2.4>对于上面指定的寄存器x5和x6的内容,以下汇编代码中x30的值是多少? \mint{asm}|add x30, x5, x6| \vspace{-1.5em} \mint{asm}|add x30, x30, x5| }{ 第一行就是2.10.1的指令,之后执行第二行,按照最高位来看就是$0101_{2}+1000_{2}=1101_{2}$,即$\mathrm{D}_{16}$,所以x30的值为 $$ 0 \mathrm{xD} 000000000000000 $$ } \questionandanswer[2.10.6]{ [ 5 ]<2.4> x30中的结果是否为预期结果,或者是否溢出? }{ 结果的最高位为1,虽然仍是负数但显然不是$\mathrm{x}5+\mathrm{x}6+\mathrm{x}5$的结果,所以不是预期结果,产生了溢出。 } \questionandanswer[2.27]{ [ 5 ]<2.7>将以下循环转换为C代码。假设C语言级的整数i保存在寄存器x5中, x6保存名为result的C语言级的整数,x10保存整数MemArray的基址。 }{} \begin{minted}{asm} addi x6, x0, 0 // result = 0 addi x29, x0, 100 // x29 = 100 LOOP: ld x7, 0(x10) // x7 = *MemArray add x5, x5, x7 // i = i + *MemArray addi x10, x10, 8 // MemArray++ addi x6, x6, 1 // result++ blt x6, x29, LOOP // (result < 100) \end{minted} {\kaishu 注释已写在上方。根据题意和命名来看, \mintinline{C}{MemArray} 应该是整数数组而不是整数吧,不然总不能 \mintinline{C}{(&MemArray)++} 吧。这里假设整数都是64位的,所以 \mintinline{C}{MemArray++} 对应的汇编代码是增加8个字节,即 \mintinline{asm}{addi x10, x10, 8 }。 根据注释分析出的结果,可以得到C代码: \begin{minted}{C} for (int result = 0; result < 100; result++) { i += *(MemArray++); } \end{minted} 为什么这里是 \mintinline{C}{result} 作为循环变量而 \mintinline{C}{i} 作为结果啊???但按照题意分析出来就是这样。 } \questionandanswer[2.31]{ [ 20 ]<2.8>将函数f转换为RISC-V汇编语言。假设g的函数声明是 \mintinline{C}{int g(int a,int b)}。函数f的代码如下: }{} \begin{minted}{C} int f(int a, int b, int c, int d) { return g(g(a,b), c+d); } \end{minted} {\kaishu 注意: % \setlist[2]{label=\alph{enumii}.,listparindent=\parindent} \begin{enumerate}[label=(\arabic{enumii})] \item 调用函数时参数应该是从右往左计算的,但是需要保存的局部变量是从左到右保存的; \item 栈指针应该是16字节(四字)对齐的; \item 返回地址应该是调用者保存的。 \end{enumerate} \begin{minted}{asm} f: // 保存自己要用到的保存寄存器,好像没有 // 此时a,b,c,d 分别保存在 x10, x11, x12, x13 中 addw x5, x12, x13 // 计算c+d,4字节整数的要加w addi sp, sp, -16 // 移动栈指针,栈指针应该是16字节对齐的? sd x1, 8(sp) // 保存x1,返回地址 sd x5, 0(sp) // 保存x5,这里int类型4个字节,由于对齐产生了空位 // a和b在调用g(a,b)后不会用到,所以不用保存 // ***开始计算g(a,b) // a和b已经在x10和x11中所以不需要移动 jal x1, g // 跳转到g // g的返回值在x10中,正好是下一次调用的第一个参数 ld x11, 0(sp) // 恢复x5,直接恢复到x11上避免再移动 // x1不需要着急恢复,马上就是下一次调用了 // 只恢复了x5,栈指针要16字节对齐不能移动 // ***开始计算g(g(a,b), c+d) // 第二个参数已从x5恢复 // 第一个参数已经在x10中 jal x1, g // 跳转到g // g的返回值在x10中,不需要移动 ld x1, 0(sp) // 恢复x1 addi sp, sp, 16 // 恢复栈指针 // 恢复保存的保存寄存器,好像没有 jalr x0, x1 // 返回 \end{minted} } \vspace{2em} \begin{verification} 使用命令 \mintinline{shell}|riscv64-unknown-linux-gnu-gcc -S main.c -o main.s| 编译。 \baselineskip=1.4em \begin{minted}{asm} f: .LFB0: .cfi_startproc addi sp,sp,-32 .cfi_def_cfa_offset 32 sd ra,24(sp) sd s0,16(sp) .cfi_offset 1, -8 .cfi_offset 8, -16 addi s0,sp,32 .cfi_def_cfa 8, 0 mv a5,a0 mv a4,a3 sw a5,-20(s0) mv a5,a1 sw a5,-24(s0) mv a5,a2 sw a5,-28(s0) mv a5,a4 sw a5,-32(s0) lw a4,-24(s0) lw a5,-20(s0) mv a1,a4 mv a0,a5 call g mv a5,a0 mv a3,a5 lw a5,-28(s0) mv a4,a5 lw a5,-32(s0) addw a5,a4,a5 sext.w a5,a5 mv a1,a5 mv a0,a3 call g mv a5,a0 mv a0,a5 ld ra,24(sp) .cfi_restore 1 ld s0,16(sp) .cfi_restore 8 .cfi_def_cfa 2, 32 addi sp,sp,32 .cfi_def_cfa_offset 0 jr ra .cfi_endproc \end{minted} \end{verification} \end{enumerate} \end{document}