这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【分享评测,赢取加热台】RISC-V 架构 lib.a 中 memcpy函数实现

共1条 1/1 1 跳转至

【分享评测,赢取加热台】RISC-V 架构 lib.a 中 memcpy函数实现解析

工程师
2024-12-19 22:23:22     打赏

简介:

我们之前使用内嵌汇编基于GCC编写了memcpy 函数,标准库里面的memcpy 函数是如何实现的呢,以下是GCC 工具链中memcpy 函数对应的汇编代码。 

lib_a-memcpy.o:     file format elf32-littleriscv
Disassembly of section .text.memcpy:
00000000 <memcpy>:
   0:  00a5c7b3              xor     a5, a1, a0
   4:  8b8d                  andi    a5, a5, 3
   6:  00c50733              add     a4, a0, a2
   a:  e781                  bnez    a5, 12 <.L2>
   c:  478d                  li      a5, 3
   e:  00c7ee63              bltu    a5, a2, 2a <.L3>
00000012 <.L2>:
  12:  87aa                  mv      a5, a0
  14:  0ae57063              bgeu    a0, a4, b4 <.L19>
00000018 <.L5>:
  18:  0005c683              lbu     a3, 0(a1)
0000001c <.LVL3>:
  1c:  0785                  addi    a5, a5, 1
  1e:  0585                  addi    a1, a1, 1
  20:  fed78fa3              sb      a3, -1(a5)
00000024 <.LBE2>:
  24:  fee7eae3              bltu    a5, a4, 18 <.L5>
  28:  8082                  ret
0000002a <.L3>:
  2a:  00357693              andi    a3, a0, 3
  2e:  87aa                  mv      a5, a0
00000030 <.L20>:
  30:  ca91                  beqz    a3, 44 <.L7>
00000032 <.LBB3>:
  32:  0005c683              lbu     a3, 0(a1)
00000036 <.LVL8>:
  36:  0785                  addi    a5, a5, 1
  38:  0585                  addi    a1, a1, 1
  3a:  fed78fa3              sb      a3, -1(a5)
0000003e <.LBE3>:
  3e:  0037f693              andi    a3, a5, 3
00000042 <.LVL11>:
  42:  b7fd                  j       30 <.L20>
00000044 <.L7>:
  44:  ffc77693              andi    a3, a4, -4
00000048 <.LVL13>:
  48:  fe068613              addi    a2, a3, -32
0000004c <.L21>:
  4c:  06c7f063              bgeu    a5, a2, ac <.L11>
00000050 <.LBB4>:
  50:  0005a383              lw      t2, 0(a1)
00000054 <.LVL16>:
  54:  0045a283              lw      t0, 4(a1)
00000058 <.LVL17>:
  58:  0085af83              lw      t6, 8(a1)
0000005c <.LVL18>:
  5c:  00c5af03              lw      t5, 12(a1)
00000060 <.LVL19>:
  60:  0105ae83              lw      t4, 16(a1)
00000064 <.LVL20>:
  64:  0145ae03              lw      t3, 20(a1)
00000068 <.LVL21>:
  68:  0185a303              lw      t1, 24(a1)
0000006c <.LVL22>:
  6c:  01c5a883              lw      a7, 28(a1)
00000070 <.LVL23>:
  70:  02458593              addi    a1, a1, 36
00000074 <.LVL24>:
  74:  0077a023              sw      t2, 0(a5)
  78:  ffc5a803              lw      a6, -4(a1)
0000007c <.LVL25>:
  7c:  0057a223              sw      t0, 4(a5)
00000080 <.LVL26>:
  80:  01f7a423              sw      t6, 8(a5)
00000084 <.LVL27>:
  84:  01e7a623              sw      t5, 12(a5)
00000088 <.LVL28>:
  88:  01d7a823              sw      t4, 16(a5)
0000008c <.LVL29>:
  8c:  01c7aa23              sw      t3, 20(a5)
00000090 <.LVL30>:
  90:  0067ac23              sw      t1, 24(a5)
00000094 <.LVL31>:
  94:  0117ae23              sw      a7, 28(a5)
  98:  02478793              addi    a5, a5, 36
0000009c <.LVL32>:
  9c:  ff07ae23              sw      a6, -4(a5)
  a0:  b775                  j       4c <.L21>
000000a2 <.L12>:
  a2:  4190                  lw      a2, 0(a1)
000000a4 <.LVL34>:
  a4:  0791                  addi    a5, a5, 4
  a6:  0591                  addi    a1, a1, 4
  a8:  fec7ae23              sw      a2, -4(a5)
000000ac <.L11>:
  ac:  fed7ebe3              bltu    a5, a3, a2 <.L12>
000000b0 <.LVL38>:
  b0:  f6e7e4e3              bltu    a5, a4, 18 <.L5>
000000b4 <.L19>:
  b4:  8082                  ret


上述汇编代码涉及的汇编指令并不多只是存储、加载 跳转指令,对上述代码的流程添加了注释,对应说明如下

memcpy:                 # void *memcpy(void *destin, void *source, unsigned n)
  xor     a5, a1, a0    #判断src 和 dst 地址的低两位地址是否相同,如果相同则地址为拷贝源地址和目的地址是对齐的
  andi    a5, a5, 3
  add     a4, a0, a2   #a4 保存拷贝数据的结束地址
  bnez    a5, .L2      #a5 != 0 跳转至L2 按照地址非同步对齐方式访问
  li      a5, 3        # 地址对齐时更新a5 = 3
  bltu    a5, a2, .L3  # a5(3) < a2(len) len > 3 跳转至L3
.L2:                   # src 和 dst 地址非对齐 及 len <= 3 会运行至此
  mv      a5, a0
  bgeu    a0, a4, .L19 # a0 > a4 函数结束,a4 = a0 + len,该条件成立说明拷贝地址发生了无符号的overflow事件
.L5:
  lbu     a3, 0(a1)    # a3 作为拷贝中间寄存器,将src[0] 数据加载到a3
.LVL3:
  addi    a5, a5, 1    # destin++
  addi    a1, a1, 1    # src++
  sb      a3, -1(a5)   # 将a3数据写入destin
.LBE2:
  bltu    a5, a4, .L5  # a5 < a4 跳转到 L5 继续拷贝一个字节,否则结束拷贝
  ret
.L3:                  # 拷贝的长度大于等于4,并且src 和 destin 地址对齐时会跳转到此处
  andi    a3, a0, 3   # 获取destin 地址的低两位地址保存至a3
  mv      a5, a0      # a5 = 获取destin
.L20:
  beqz    a3, .L7     # destin 和 src 地址是4字节对齐,跳转至 L7 处理
.LBB3:
  lbu     a3, 0(a1)   # 读取src[] 至 a3
.LVL8:
  addi    a5, a5, 1   # destin++
  addi    a1, a1, 1   # src++
  sb      a3, -1(a5)  # dest[] = a3
.LBE3:
  andi    a3, a5, 3   # 更新 destin 地址的低两位地址保存至a3
.LVL11:
  j       .L20        # 跳转至L20,处理非四字节对齐部分的拷贝
.L7:
  andi    a3, a4, -4  # 将destin + len 地址向下4字节对齐保存至a3
.LVL13:
  addi    a2, a3, -32 # a2 = a3 - 32,作为循环结束条件判断
.L21:
  bgeu    a5, a2, .L11 #a5 > a2 说明要拷贝的字节数小于32 跳转到L11 处理
.LBB4:
  lw      t2, 0(a1)    # 拷贝32字节
.LVL16:
  lw      t0, 4(a1)
.LVL17:
  lw      t6, 8(a1)
.LVL18:
  lw      t5, 12(a1)
.LVL19:
  lw      t4, 16(a1)
.LVL20:
  lw      t3, 20(a1)
.LVL21:
  lw      t1, 24(a1)
.LVL22:
  lw      a7, 28(a1)
.LVL23:
  addi    a1, a1, 36
.LVL24:
  sw      t2, 0(a5)
  lw      a6, -4(a1)
.LVL25:
  sw      t0, 4(a5)
.LVL26:
  sw      t6, 8(a5)
.LVL27:
  sw      t5, 12(a5)
.LVL28:
  sw      t4, 16(a5)
.LVL29:
  sw      t3, 20(a5)
.LVL30:
  sw      t1, 24(a5)
.LVL31:
  sw      a7, 28(a5)
.LVL32:
  addi    a5, a5, 36
.LVL33:
  sw      a6, -4(a5)
  j       .L21
.L12:               #按照4字节为代为进行拷贝
  lw      a2, 0(a1)
.LVL34:
  addi    a5, a5, 4
  addi    a1, a1, 4
  sw      a2, -4(a5)
.L11:
  bltu    a5, a3, .L12
.LVL38:             # 拷贝剩余非四字节对齐的数据,按照字节拷贝
  bltu    a5, a4, .L5
.L19:
  ret

对应汇编代码的实现流程如下:

1. 如果src  及 destin 的地址是非对齐,或者拷贝数据 < 4 字节 则按照字节进行拷贝,拷贝结束返回

==》 因为地址是非对齐的,以src 地址为1 dest 地址为2 为例,这两个地址同时加一,永远不会两地址同时为4的倍数,使用sl/sb 字节就进行拷贝,避免处理器使用lw/sw地址非4字节对齐访问出发的硬件异常

2. 如果 src  及 destin 的地址是对齐 并且拷贝的长度大于4,先按照字节拷贝非4字节对齐部分。

3.按照字节拷贝完非四字对齐部分后,剩余长度> 32 则按照32字节为单位进行拷贝。

==>这么拷贝都是使用LW/SW指令进行拷贝和4字节拷贝使用的指令是一致的,连续拷贝8次,这么拷贝的目的代码会减少判断拷贝快结束的次数提高代码执行效率

4.拷贝完32字节后,如果剩余长度 >4 ,剩余部分如果大于四字节按照4字节为单位进行拷贝

5.按照四字节拷贝后剩余长度> 0,按照字节为单位拷贝剩余内容


以下以destin = 0x100100c1 ,src = 0x10010001, 拷贝64 字节为例说明整个拷贝流程

1.  按照1字节为单位拷贝0x10010001~0x10010003 3字节0x100100c1 ~ 0x100100c3 地址,处理后destin 和 src 地址已经按照四字节对齐

2. 按照32字节为单位拷贝0x10010004 ~  0x10010023 至 0x100100C4~ 0x100100E3 空间,拷贝32字节数据

3.按照4字节为单位拷贝28字数据

3.按照1字节为单位拷贝1数据


整个复制过程分为四段进行拷贝,从中可以看出库函数实现上充考虑了地址对齐访问的场景,同时最多拷贝32字节,以数据块的方式来拷贝减少判断次数提高代码的执行效率。



共1条 1/1 1 跳转至

回复

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