Unsigned Int In RISC-V

Table of Contents

1. Unsigned Int In RISC-V

https://forums.sifive.com/t/how-can-i-repeat-the-coremark-score/1947

#include <stdint.h>

#ifdef BAD
typedef uint32_t INT;
#else
typedef int32_t INT;
#endif

void matrix_add_const(INT N, INT *A, INT val) {
    INT i, j;
    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            A[i * N + j] += val;
        }
    }
}
// good:
// riscv64-linux-gnu-gcc-10 test.c -Ofast -c
0000000000000000 <matrix_add_const>:
   0:   02a05963                blez    a0,32 <.L1>
   4:   fff5069b                addiw   a3,a0,-1
   8:   1682                    slli    a3,a3,0x20
   a:   82f9                    srli    a3,a3,0x1e
   c:   00458793                addi    a5,a1,4
  10:   00251893                slli    a7,a0,0x2
  14:   96be                    add     a3,a3,a5
  16:   4801                    li      a6,0

0000000000000018 <.L3>:
  18:   87ae                    mv      a5,a1 # a1 是 i*N

000000000000001a <.L4>:
  1a:   4398                    lw      a4,0(a5)
  1c:   0791                    addi    a5,a5,4
  1e:   9f31                    addw    a4,a4,a2
  20:   fee7ae23                sw      a4,-4(a5)
  24:   fef69be3                bne     a3,a5,1a <.L4>
  28:   2805                    addiw   a6,a6,1
  2a:   95c6                    add     a1,a1,a7
  2c:   96c6                    add     a3,a3,a7
  2e:   ff0515e3                bne     a0,a6,18 <.L3>

0000000000000032 <.L1>:
  32:   8082                    ret

// bad:
// riscv64-linux-gnu-gcc-10 test.c -Ofast -c -DBAD
0000000000000000 <matrix_add_const>:
   0:   882a                    mv      a6,a0
   2:   4301                    li      t1,0
   4:   4881                    li      a7,0
   6:   c505                    beqz    a0,2e <.L9>

0000000000000008 <.L2>:
   8:   871a                    mv      a4,t1

000000000000000a <.L4>:

,---- 计算偏移量时需要做无符号扩展
|    a:   02071793                slli    a5,a4,0x20
|    e:   83f9                    srli    a5,a5,0x1e
`----
  10:   97ae                    add     a5,a5,a1
  12:   4394                    lw      a3,0(a5)
  14:   2705                    addiw   a4,a4,1
  16:   9eb1                    addw    a3,a3,a2
  18:   c394                    sw      a3,0(a5)
  1a:   fee818e3                bne     a6,a4,a <.L4>
  1e:   2885                    addiw   a7,a7,1
  20:   0065033b                addw    t1,a0,t1
  24:   0105083b                addw    a6,a0,a6
  28:   ff1510e3                bne     a0,a7,8 <.L2>
  2c:   8082                    ret

rv64 下 uint32_t 需要显式的无符号扩展的场景有:

  1. 涉及到宽度有变化的类型转换, 例如 uint32_t -> int64_t

    例如:

    #include <stdint.h>
    
    int64_t foo(uint32_t N) { return N; }
    
    0000000000000000 <foo>:
       0:   1502                    slli    a0,a0,0x20
       2:   9101                    srli    a0,a0,0x20
       4:   8082                    ret
    
  2. 像上面的例子, 做为地址使用. 实际上也是 uint32_t -> int64_t, 因为 riscv 内存寻址时必须使用整个基地址寄存器的所有 bit (例如它没有办法只使用 a0 的低 32 位)

上面的问题最根本的原因是, int32_t/uint32_t 的在做为地址时需要扩展成 64bit, 而 riscv 大部分指令会自动做符号扩展, 所以针对 int32_t 的符号扩展可以省掉, 但无符号扩展则必须通过显式的 zext 类指令(例如 slli+srli), 所以 rv64 上涉及访存的变量用 int32_t 性能好于 uint32_t. 实际上我们应当使用 size_t 来索引数组(rv64 下 size_t 为 uint64_t, rv32 下 size_t 为 uint32_t)

另外, 由于符号扩展会在高位加入符号位, 所以涉及 bit 操作时用 signed 则可能出错, 例如:

// 2023-04-27 20:48
#include <stdint.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int8_t tmp = -1;
    int8_t sign = tmp & (1 << 7);
    /* NOTE: gcc 先把左侧 sign 符号扩展成 int32_t, 为 0xffffff80, 右侧也是以
     * int32_t 为计算, 导致结果为 0x00000080. 如果 int8_t 换成 uint8_t 则没有问
     * 题, 因为没有符号扩展. 所以 bit 操作最好用 unsigned */
    if (sign == (tmp & (1 << 7))) {
        printf("ok\n");
    }
    return 0;
}

综上, 由于有符号/无符号扩展的原因, riscv 用 signed int 可能性能好一些, 但 unsigned int 更不容易出一些奇怪的错误.

Author: [email protected]
Date: 2023-11-17 Fri 11:42
Last updated: 2023-11-20 Mon 11:03

知识共享许可协议