👾

RISC-VのVector Extensionでメモリをゼロクリアするコード

2023/02/23に公開

前回の記事でmemcpyをベクター命令で実行するサンプルコードがありました。
それならということで、メモリをゼロクリアする関数を書いたらベクター命令を使ってどんなコードが生成されるかを試してみました。

memclr関数

memclr.c
#include <stddef.h>
#include <stdint.h>

void memclr(void *s, size_t n)
{
	uint8_t *p = (uint8_t*)s;
	while (n-- > 0) {
		*p++ = 0;
	}
}

1回目(失敗)

$ clang-16 -c  -march=rv64gcv -target riscv64 -O2 memclr.c
$ llvm-objdump -dr memclr.o

memclr.o:	file format elf64-littleriscv

Disassembly of section .text:

0000000000000000 <.text>:
       0: 01 00        	nop
		0000000000000000:  R_RISCV_ALIGN	*ABS*+0x2

0000000000000002 <memclr>:
       2: 81 c1        	beqz	a1, 0x2 <memclr>
		0000000000000002:  R_RISCV_RVC_BRANCH	.LBB0_2
       4: 2e 86        	mv	a2, a1
       6: 81 45        	li	a1, 0
       8: 17 03 00 00  	auipc	t1, 0
		0000000000000008:  R_RISCV_CALL_PLT	memset
		0000000000000008:  R_RISCV_RELAX	*ABS*
       c: 67 00 03 00  	jr	t1

0000000000000010 <.LBB0_2>:
      10: 82 80        	ret

あれ?意外に短いな。
よくみると、これはmemset(s, 0, n);を呼び出しているだけじゃん ! (笑)
というか、標準Cライブラリのmemsetがそのシステム向けにチューニングされていると仮定すれば、普通に賢いですね。
clang 「そんなん、memsetで一発ですわ。」

2回目

$ clang-16 --help |less

で調べると -ffreestandingをつければ標準Cライブラリに依存しないコードになりそう。

$ clang-16 -c  -ffreestanding -march=rv64gcv -target riscv64 -O2 memclr.c
$ llvm-objdump -dr memclr.o

memclr.o:	file format elf64-littleriscv

Disassembly of section .text:

0000000000000000 <.text>:
       0: 01 00        	nop
		0000000000000000:  R_RISCV_ALIGN	*ABS*+0x2

0000000000000002 <memclr>:
       2: 81 c1        	beqz	a1, 0x2 <memclr>
		0000000000000002:  R_RISCV_RVC_BRANCH	.LBB0_7
       4: 73 28 20 c2  	csrr	a6, vlenb
       8: 93 17 18 00  	slli	a5, a6, 1
       c: 63 f0 f5 00  	bgeu	a1, a5, 0xc <memclr+0xa>
		000000000000000c:  R_RISCV_BRANCH	.LBB0_3
      10: aa 86        	mv	a3, a0
      12: 2e 86        	mv	a2, a1
      14: 01 a0        	j	0x14 <memclr+0x12>
		0000000000000014:  R_RISCV_RVC_JUMP	.LBB0_6

0000000000000016 <.LBB0_3>:
      16: 13 86 f7 ff  	addi	a2, a5, -1
      1a: 6d 8e        	and	a2, a2, a1
      1c: 91 8d        	sub	a1, a1, a2
      1e: b3 06 b5 00  	add	a3, a0, a1
      22: 57 77 00 0c  	vsetvli	a4, zero, e8, m1, ta, ma
      26: 57 34 00 5e  	vmv.v.i	v8, 0

000000000000002a <.LBB0_4>:
      2a: 27 04 85 02  	vs1r.v	v8, (a0)
      2e: 33 07 05 01  	add	a4, a0, a6
      32: 27 04 87 02  	vs1r.v	v8, (a4)
      36: 9d 8d        	sub	a1, a1, a5
      38: 3e 95        	add	a0, a0, a5
      3a: 81 e1        	bnez	a1, 0x3a <.LBB0_4+0x10>
		000000000000003a:  R_RISCV_RVC_BRANCH	.LBB0_4
      3c: 01 c2        	beqz	a2, 0x3c <.LBB0_4+0x12>
		000000000000003c:  R_RISCV_RVC_BRANCH	.LBB0_7

000000000000003e <.LBB0_6>:
      3e: 7d 16        	addi	a2, a2, -1
      40: 13 85 16 00  	addi	a0, a3, 1
      44: 23 80 06 00  	sb	zero, 0(a3)
      48: aa 86        	mv	a3, a0
      4a: 01 e2        	bnez	a2, 0x4a <.LBB0_6+0xc>
		000000000000004a:  R_RISCV_RVC_BRANCH	.LBB0_6

000000000000004c <.LBB0_7>:
      4c: 82 80        	ret

うまくベクター命令使っています。
.LBB0_4のブロックを見てください。1回のループでベクタレジスタ2つ分をゼロクリアしていますね。

関連

https://zenn.dev/tetsu_koba/articles/37b23424bb252b

Discussion