跳转至

程序的机器级表示

常数, 变量, 运算

32位常数的装入

int f() { return 0x123; /* 291 */ }
int g() { return -1; }
int h() { return 0x1234; /* 4660 */ }
int i() { return 0xbb8; /* 3000 */ }
- 执行指令: - rv32gcc -O2 -c a.c - rvobjdump -M no-alias -d a.o - 结果:
a.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <f>:
   0:   12300513                addi    a0,zero,291
   4:   00008067                jalr    zero,0(ra)

00000008 <g>:
   8:   fff00513                addi    a0,zero,-1
   c:   00008067                jalr    zero,0(ra)

00000010 <j>:
  10:   00001537                lui     a0,0x1
  14:   23450513                addi    a0,a0,564 # 1234 <i+0x1218>
  18:   00008067                jalr    zero,0(ra)

0000001c <i>:
  1c:   00001537                lui     a0,0x1
  20:   bb850513                addi    a0,a0,-1096 # bb8 <i+0xb9c>
  24:   00008067                jalr    zero,0(ra)
- 解析: - f: 指令的立即数位数足够, 直接使用addi指令 - g: 指令的立即数位数足够, 直接使用addi指令(立即数为有符号数) - h: addi指令的立即数位数不足以一次性完成操作, 因此先加载高20位(lui), 再加载低12位(addi): 0x1 << 12 + 0x234 - i: bb8只有三位, 但由于第11为为1, 经过立即数的符号拓展后, 会出现负数, 并不是我们想要的bb8, 因此, 同样的, 先加载高20位, 在利用立即数的符号拓展, 减去一个数: 0x1 << 12 + 0xbb8(12位补码, 为-1096) - 根据addi的语义, imm[11:0]需要进行符号拓展 - 相当于, 对lui的结果进行±2048的范围内进行调整

64位常数在RV64中的装入

long long j() { return 0x1234567800000000; }
long long k() { return 0x1234567887654321; }
- 指令: riscv64-gnu-linux -O2 -c b.c - 汇编代码:
        .file   "b.c"
        .option pic
        .text
        .align  1
        .globl  j
        .type   j, @function
j:
        li      a0,38178816
        addi    a0,a0,-1329
        slli    a0,a0,35
        ret
        .size   j, .-j
        .align  1
        .globl  k
        .type   k, @function
k:
        ld      a0,.LC0
        ret
        .size   k, .-k
        .section        .rodata.cst8,"aM",@progbits,8
        .align  3
.LC0:
        .dword  1311768467139281697
        .ident  "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
        .section        .note.GNU-stack,"",@progbits
- 解析: - j: 38178816 << 35 + (-1329) - k: 直接将对应的立即数存储在内存中, 使用ld从内存中加载

[!note] 为什么从内存中加载 如果一个立即数位数比较长, gcc会选择直接从内存中加载, 用来换取更短的指令序列. 指令太多并不好, 在乱序执行的高性能CPU中, 取指令的代价大于取数据: - 在流水线中, 指令是数据的上游 - 如果从内存中取数据, 只需要让取数逻辑等待, 可以先执行其他无关的指令 - 如果从内存中取指令, 则整条流水线都需要等待指令

64位常数在RV32中的装入

  • 指令: rv32gcc -O2 -S b.c
  • 编译结果:
            .file   "b.c"
            .option pic
            .text
            .align  2
            .globl  j
            .type   j, @function
    j:
            li      a1,305418240
            li      a0,0
            addi    a1,a1,1656
            ret
            .size   j, .-j
            .align  2
            .globl  k
            .type   k, @function
    k:
            lla     a5,.LC0
            lw      a0,0(a5)
            lw      a1,4(a5)
            ret
            .size   k, .-k
            .section        .rodata.cst8,"aM",@progbits,8
            .align  3
    .LC0:
            .word   -2023406815
            .word   305419896
            .ident  "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
            .section        .note.GNU-stack,"",@progbits
    
  • 解析: 使用两个32位的寄存器联合存放
  • 使用clang进行编译:
            .text
            .attribute      4, 16
            .attribute      5, "rv32i2p0_m2p0_a2p0_c2p0"
            .file   "b.c"
            .globl  j
            .p2align        1
            .type   j,@function
    j:
            lui     a0, 74565
            addi    a1, a0, 1656
            li      a0, 0
            ret
    .Lfunc_end0:
            .size   j, .Lfunc_end0-j
    
            .globl  k
            .p2align        1
            .type   k,@function
    k:
            lui     a0, 554580
            addi    a0, a0, 801
            lui     a1, 74565
            addi    a1, a1, 1656
            ret
    .Lfunc_end1:
            .size   k, .Lfunc_end1-k
    
            .ident  "Ubuntu clang version 14.0.0-1ubuntu1.1"
            .section        ".note.GNU-stack","",@progbits
            .addrsig