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 需要显式的无符号扩展的场景有:
涉及到宽度有变化的类型转换, 例如 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
- 像上面的例子, 做为地址使用. 实际上也是 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 更不容易出一些奇怪的错误.