这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【分享评测,赢取加热台】RISC-V GCC 内嵌汇编使用

共1条 1/1 1 跳转至

【分享评测,赢取加热台】RISC-V GCC 内嵌汇编使用

工程师
2024-12-13 11:23:58   被打赏 38 分(兑奖)     打赏

 简介

内嵌汇编代码是指在C语言中嵌入汇编代码(Inline Assembly Language in C Code),可以在对代码时间要求比较高,及在C语言中访问某些汇编指令来实现特殊功能,内嵌汇编代码主要有两种形式:基础内嵌汇编代码(basic asm):不带任何参数扩展内嵌汇编代码(extended asm):可以带输入/输出参数

扩展内嵌汇编的模板如下

asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])

asm 修饰词 (
指令部分
:输出部分
:输入部分
:损害部分
)

常见的约束体条件如下:

= 被修饰的操作数具有只写属性

“+” 被修饰的操作数具有可读可写属性

 & 用于输出限定符,这个操作数在输入参数指令执行完成后才能写入。

 r 通用寄存器

p 内存地址

m 内存变量

i 立即数

内嵌汇编代码验证1

学习内嵌汇编代码,我们将之前汇编实现的my_memcpy 代码按照函数调用规划a0 传递数据源地址,a1 传递目的地址,a2 传递拷贝长度,返回拷贝长度通过a0 返回,以下是 rars 验证通过的汇编代码。

.global my_memcpy_test
.text

li t0, 0x10010000
li t1, 0x11111111
sw t1, (t0)
li t1, 0x22222222
sw t1, 4(t0)
li t1, 0x33333333
sw t1, 8(t0)
li t1, 0x44444444
sw t1, 12(t0)
li t1, 0x55555555
sw t1, 16(t0)
li t1, 0x66666666
sw t1, 20(t0)
li t1, 0x77777777
sw t1, 24(t0)
li t1, 0x88888888
sw t1, 28(t0)
li t1, 0x99999999
sw t1, 32(t0)
li t1, 0xaaaaaaaa
sw t1, 36(t0)
li t1, 0xbbbbbbbb
sw t1, 40(t0)

li a0, 0x10010000
li a1, 0x100100c0
li a2, 32

my_memcpy_test:
    mv t0, a0
    mv t1, a1
    add t2, t0, a2

.loop:
    lw t3, (t0)
    sw t3, 0(t1)
    addi t0, t0, 4
    addi t1, t1, 4
    blt t0, t2, .loop

    mv a0,a2

    ret

将上述代码按照转化以下内嵌汇编代码:

int my_memcpy(void * src,void *  dest ,int len)
{
    asm volatile (
    "mv t0, a0 \n"
    "mv t1, a1 \n"
    "add t2, t0, a2 \n"
    ".loop: \n"
    "lw t3, (t0) \n"
    "sw t3, 0(t1) \n"
    "addi t0, t0, 4 \n"
    "addi t1, t1, 4 \n"
    "blt t0, t2, .loop\n"
    "mv a0,a2 \n"
    "ret \n"
    :::
    );
}

在main函数内添加测试代码调用my_mempy 函数,验证函数工作状态。

int main(void)
{

    int src[8] = {0x11111111,0x22222222,0x33333333,0x44444444,
                   0x55555555,0x66666666,0x7777777,0x88888888};

    int dest[8] = {0x00};

    int ret = my_memcpy((void*) src,(void *)dest,32);

    for(int i = 0;i < 8;i++)
        printf("dest[%d] = %x,",i,dest[i]);
    printf("ret = %d\r\n",ret);

}

运行后数据已经按照预期的进行了拷贝,函数返回值也已经按照预期的打印输出32.

dest[0] = 11111111,dest[1] = 22222222,dest[2] = 33333333,dest[3] = 44444444,dest[4] = 55555555,dest[5] = 66666666,dest[6] = 7777777,dest[7] = 88888888,ret = 32

反汇编 产看编译器产生的汇编代码如下:

30079462 <my_memcpy>:
30079462: 82aa                mv t0,a0
30079464: 832e                mv t1,a1
30079466: 00c283b3          add t2,t0,a2

3007946a <.loop>:
3007946a: 0002ae03          lw t3,0(t0)
3007946e: 01c32023          sw t3,0(t1)
30079472: 0291                addi t0,t0,4
30079474: 0311                addi t1,t1,4
30079476: fe72cae3          blt t0,t2,3007946a <.loop>
3007947a: 8532                mv a0,a2
3007947c: 8082                ret
3007947e: 8082                ret

30079480 <main>:
30079480: 1101                addi sp,sp,-32
30079482: 02000613          li a2,32
30079486: 0ff89597          auipc a1,0xff89
3007948a: c3258593          addi a1,a1,-974 # 400020b8 <dest.0>
3007948e: 00043517          auipc a0,0x43
30079492: 4aa50513          addi a0,a0,1194 # 300bc938 <src.1>
30079496: cc22                sw s0,24(sp)
30079498: ca26                sw s1,20(sp)
3007949a: c84a                sw s2,16(sp)
3007949c: c64e                sw s3,12(sp)
3007949e: c452                sw s4,8(sp)
300794a0: ce06                sw ra,28(sp)
300794a2: fc1ff0ef          jal ra,30079462 <my_memcpy>
300794a6: 8a2a                mv s4,a0
300794a8: 4401                li s0,0

内嵌汇编代码验证2

上面的内嵌汇编的代码直接使用的汇编代码实现的,并没有使用变量,我们再次基础上修改内嵌汇编的代码使用变量的方式实现上述功能,修改代码如下。

static int my_memcpy1(void * src,void *  dst ,int len)
{
unsigned long tmp = 0;
unsigned long end = (char *)src + len;

asm volatile (
"1: lw %1, (%2)\n"   //tmp = *src
"sw %1, (%0)\n"      //*dst  = tmp
"addi %0, %0, 4\n"   //dst += 4
"addi %2, %2, 4\n"   //src += 4
"blt %2, %3, 1b"     //if(src < end) goto 1
: "+r" (dst), "+r" (tmp), "+r" (src)
: "r" (end)
: "memory");
return len;
}

添加测试代码在main 函数中调用my_memcpy1 函数

int main(void)
{
  static int src[8] = {0x11111111,0x22222222,0x33333333,0x44444444,
                0x55555555,0x66666666,0x7777777,0x88888888};

  static int dest[8] = {0x00};

  int ret = my_memcpy1((void*) src,(void *)dest,32);

  for(int i = 0;i < 8;i++)
    printf("dest[%d] = %x,",i,dest[i]);
  
  printf("ret = %d\r\n",ret);
}

上述代码运行结果跟之前也是一致说明功能已经ok,查看反汇编代码如下

40034284 <main>:
40034284: 1101                addi sp,sp,-32
40034286: ca26                sw s1,20(sp)
40034288: 0007c497          auipc s1,0x7c
4003428c: 98048493          addi s1,s1,-1664 # 400afc08 <dest.0>
40034290: ce06                sw ra,28(sp)
40034292: cc22                sw s0,24(sp)
40034294: c84a                sw s2,16(sp)
40034296: c64e                sw s3,12(sp)
40034298: 87a6                mv a5,s1
4003429a: 4701                li a4,0
4003429c: 00078697          auipc a3,0x78
400342a0: 1c868693          addi a3,a3,456 # 400ac464 <src.1>
400342a4: 00078617          auipc a2,0x78
400342a8: 1e060613          addi a2,a2,480 # 400ac484 <aic_audio_ops>
400342ac: 4298                lw a4,0(a3)
400342ae: c398                sw a4,0(a5)
400342b0: 0791                addi a5,a5,4
400342b2: 0691                addi a3,a3,4
400342b4: fec6cce3          blt a3,a2,400342ac <main+0x28>

发现编译器将 my_memcpy1 函数进行了编译优化,进行了inline 处理,修改my_memcpy1 函数的编译属性__attribute__((noinline))查看反汇编代码如下。

40034264 <my_memcpy1>:
40034264:    00c506b3        add    a3,a0,a2 //end = src + len
40034268:    4701          li    a4,0 //tmp = 0
4003426a:    87aa          mv    a5,a0 //a5 = src
4003426c:    4398          lw    a4,0(a5) //tmp = *src 
4003426e:    c198          sw    a4,0(a1) //*dst = tmp
40034270:    0591          addi    a1,a1,4 //dst += 4
40034272:    0791          addi    a5,a5,4 //src += 4
40034274:    fed7cce3        blt    a5,a3,4003426c <my_memcpy1+0x8>//if(src < end) goto 4003426c
40034278:    8532          mv    a0,a2
4003427a:    8082          ret

从上述反汇编代码可以看出编译器对寄存器分区配情况传递情况,也是符合RISC-V ABI 接口调用规范

src 通过 a0/a5 传递
dst 通过 a1 传递
len 通过 a2 传递
将end 变量分配了a3寄存器
tmp 变量分配了a4 寄存器
返回值通过 a0 返回

内嵌汇编代码验证3

在方式2 所使用的内嵌汇编代码中使用 0% 1% 2% 这种方式来表示参数,相对直接使用符号表示变量的方式不够直接,Gcc 内嵌汇编也是支持用符号明来代替%0 %1 %2,以下是Gcc 对符号的使用示例代码。

uint32_t Mask = 1234;
uint32_t Index;
asm ("bsfl %[aMask], %[aIndex]"
: [aIndex] "=r" (Index)
: [aMask] "r" (Mask)
: "cc");
Here are some more examples of output operands.
uint32_t c = 1;
uint32_t d;
uint32_t *e = &c;
asm ("mov %[e], %[d]"
: [d] "=rm" (d)
: [e] "rm" (*e));

参照上述代码,我们在方式2的基础上继续修改代码。

static __attribute__((noinline)) int my_memcpy2(void * src,void *  dst,int len)
{
    unsigned int tmp = 0;
    unsigned int end = (char *)src + len;

    asm volatile (
            "1: lw %[tmp], (%[src])\n"
            "sw %[tmp], (%[dst])\n"
            "addi %[dst], %[dst], 4\n"
            "addi %[src], %[src], 4\n"
            "blt %[src], %[end], 1b"
            : [dst] "+r" (dst),[tmp] "+r" (tmp),[src] "+r" (src)
            : [end] "r" (end)
            : "memory");

    return len;
}

将之前的 %0 使用 [dst] 来代替,其它% 也替换成对应的符号这样相对内嵌汇编代码阅读起来相对更容易些,不过本质还是一样的上述代码对应的反汇编代码和方式2是一致。


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]