\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}