以下程序均是由一个网名:comcat 的人写的(http://comcat.blog.openrays.org/blog-htm-do-showone-tid-423.html)
最近看了一下,加上了注释与自己的理解,不对之处,请大家指出,相互探讨
针对2F 优化过的godson_memcpy.S:
/* Copyright (C) 2002, 2003 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Hartvig Ekner , 2002.
Ported to mips3 n32/n64 by Alexandre Oliva
Optimized for godson2e by comcat
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */
#include
#include
#include "regdef.h"
/* void *godson2e_memcpy(void *s1, const void *s2, size_t n);
a0 <-- s1(dest);a1 <-- s2(src);a2 <-- n
This could probably be optimized further. */
#if __BYTE_ORDER == __BIG_ENDIAN
# define LDHI ldl /* high part is left in big-endian */
# define SDHI sdl /* high part is left in big-endian */
# define LDLO ldr /* low part is right in big-endian */
# define SDLO sdr /* low part is right in big-endian */
#else
# define LDHI ldr /* high part is right in little-endian */
# define SDHI sdr /* high part is right in little-endian */
# define LDLO ldl /* low part is left in little-endian */
# define SDLO sdl /* low part is left in little-endian */
#endif
/* ENTRY (godson2e_memcpy)*/
.global godson2e_memcpy # 声明为godson2e_memcpy全局函数
.ent godson2e_memcpy # 标记函数 godson2e_memcpy 的起点位置
godson2e_memcpy:
.set noreorder # 禁止汇编器指令自动排序模式;默认情况下,汇编器处于reorder 的模式下,该模式允许汇编器对指令进行重新排序,以提高流水线的性能
.set mips3 # 告诉汇编器使用mips III 指令;mips 的指令发展至今共有以下标准:mips I-->mips II-->mips III-->mips IV-->mips V --> mips 32/64 ,后面的向前面兼容
slti t0, a2, 16 # a2 小于 16 t0 为 1,否则为 0;此处之所以同16 比较是因为 15 = 8 + 7;虽然可以用一条指令 load/store 8 个字节,但 load/ 8个字节需要考虑对齐的问题,加上的剩下的数目(a2 - 8)也能只一次 load/store 1个字节;所以总的代码执行效率还不如一次 load/store 1个字节,总共load/store a2次,因为load/store 一个字节时不要考虑对齐的问题
bne t0, zero, last16 # t0 不等于 0 (即a2 小于 16) 则跳转至 last16
move v0, a0 # 目标指针作为返回值写入v0,注意此指令位于延迟槽中(即先执行指令再跳转至 last16)
# or t0, a1, a0 ?
# 此处主要是判断字节对齐(如若CPU 访问了非对齐地址会使CPU 引起异常,典型的是提示:总线错误,然后直接杀死进程).由于godson2e 是64位的寄存器,可以一次性 load/store 8个字节,同比使用 lw/sw 指令一次性 load/store 4个字节,这样可以减少指令的执行次数,从而提高运行效率.(因为这里一次性 load/store 8 个字节,根据mips的对齐特性,必须从8字节的边界加载(即地址是8的整数倍))
xor t0, a1,a0 # a1 与 a0 相异或,结果存入 t0
andi t0, 0x7 # t0 与 0x7 相与,结果存入 t0(即判断a1,a0的低三位是否相同,若相同则t0 为 0,否则为 1).
bne t0, zero, shift # t0 不等于 0 则跳转至 shift(即a1,a0 的低三不相同;则a1,a0 至少有一个不对齐,或两个都不对齐);
PTR_SUBU t1, zero, a1 # 0 无符号减 a1 结果存入 t1(此指令位于延迟槽中);t1 的值即为 -a1 的补码
# 下面的判断是 a0,a1都对齐,或者都不对齐(因为 t1 = 0,a0,a1的低三位可能都是111 或者 000)
andi t1, 0x7 # t0 与 0x7 相与,结果存入 t0(即判断a1的低三位是111 或 000,若为 000 则t0 为 0,否则为 1).
beq t1, zero, chk8w # t0 等于 0(即a1 对齐) 则跳转至 chk8w
PTR_SUBU a2, t1 # a2 减 t1(t1 为 1) 结果存入a2(此指令位于延迟槽中)
#此处使用非对齐访问指令,不是知向左与向右的工作区别
LDHI t0, 0(a1) # a1 (低三位为111)未对齐,使用非对齐访问指令 双精度向右加载
PTR_ADDU a1, t1 # a1 加 t1 结果存入 a1
SDHI t0, 0(a0) # 双精度向右存储
PTR_ADDU a0, t1 # a0 加 t1 结果存入 a0
chk8w:
andi t0, a2, 0x3f # a2 与 0x3f 结果存入 t0;若 a2 小于或等于 63 则 t0 等于 a2 否则不相等
beq t0, a2, chk1w # 若t0 等于 a2 则跳转至 chk1w;即 a2 小于 64
PTR_SUBU a3, a2, t0 # a2 减 t0 结果存入 a3; a3 = a2 - a2 % 64;注意此指令位于延迟槽中
PTR_ADDU a3, a1 # a3 = a3 + a1; 用作下面循环是否结束的判断对比值
move a2, t0 # a2 = t0 = a2 % 64; 即把 a2 除去 64 后的得到的余数留作后面处理;此指令位于延迟槽中
lop8w: # 一次性 load/store 64 个字节的循环
ld t0, 0(a1) # 一次性load 8 个字节
ld t1, 8(a1)
ld t2, 16(a1)
ld t3, 24(a1)
ld ta0, 32(a1)
ld ta1, 40(a1)
ld ta2, 48(a1)
ld ta3, 56(a1)
PTR_ADDIU a0, 64 # a0 = a0 + 64;地址偏移64个字节
PTR_ADDIU a1, 64
sd t0, -64(a0) # 一次性store 8 个字节
sd t1, -56(a0)
sd t2, -48(a0)
sd t3, -40(a0)
sd ta0, -32(a0)
sd ta1, -24(a0)
sd ta2, -16(a0)
bne a1, a3, lop8w # a1 不等于 a3 则跳转至 lop8w
sd ta3, -8(a0) # 注意此指令位于延迟槽中
chk1w:
andi t0, a2, 0x7 # a2 与 0x7 结果存入 t0;若 a2 小于或等于 7 则 t0 等于 a2 否则不相等
beq t0, a2, last16 # 若t0 等于 a2 则跳转至 chk1w;即 a2 小于 8
PTR_SUBU a3, a2, t0 # a2 减 t0 结果存入 a3; a3 = a2 - a2 % 8;注意此指令位于延迟槽中
PTR_ADDU a3, a1 # a3 = a3 + a1 ; 用作下面循环是否结束的判断对比值
move a2, t0 # a2 = t0 = a2 % 8; 即把 a2 除去 8 后的得到的余数留作后面处理 ;此指令位于延迟槽中
lop1w: # 一次性 load/store 8 个字节的循环
ld t0, 0(a1) # 一次性store 8 个字节
PTR_ADDIU a0, 8 # a0 = a0 + 8;地址偏移8个字节
PTR_ADDIU a1, 8
bne a1, a3, lop1w # a1 不等于 a3 则跳转至 lop1w
sd t0, -8(a0) # 注意此指令位于延迟槽中
last16: # 这段使用了两个跳转指令,解决了一个通过平时的循环处理(最大要执行 15次跳转指令)的问题,大大的提高了运行效率
blez a2, 2f # a2 小于或等于 0,则跳转至 下面第一次出现标号 2 的位置
addiu a2, a2, -1 # a2 = a2 + (-1) = a2 + 0xFFFFFFFF
sll a2, a2, 2 # a2 向左移 2 位;即 a2=a2 * 4;因为mips下指令的长度占4个字节
la a3, 1f # 载入下面第一次出现标号 1 的位置的地址到 a3
subu a3, a3, a2 # a3 = a3(1f) - a2
lb $15, 0(a1) #
jr a3 # 跳转则 a3,此时 a3 的值为 标号 1 处 减去 a2 的地址
addiu a3, 2f-1f # a3 = a2 + 标号2处 的地址;注意此指令位于延迟槽中
lb $16, 15(a1)
lb $17, 14(a1)
lb $18, 13(a1)
lb $19, 12(a1)
lb $20, 11(a1)
lb $21, 10(a1)
lb $22, 9(a1)
lb $23, 8(a1)
lb $8, 7(a1)
lb $9, 6(a1)
lb $10, 5(a1)
lb $11, 4(a1)
lb $12, 3(a1)
lb $13, 2(a1)
lb $14, 1(a1)
1: jr a3 # 跳转则 a3,此时 a3 的值为 标号 2 处的地址 + a2 的地址
sw $15, 0(a0)
sb $16, 15(a0)
sb $17, 14(a0)
sb $18, 13(a0)
sb $19, 12(a0)
sb $20, 11(a0)
sb $21, 10(a0)
sb $22, 9(a0)
sb $23, 8(a0)
sb $8, 7(a0)
sb $9, 6(a0)
sb $10, 5(a0)
sb $11, 4(a0)
sb $12, 3(a0)
sb $13, 2(a0)
sb $14, 1(a0)
2: jr ra # 返回
nop
shift:
PTR_SUBU a3, zero, a0 # 0 无符号减 a0 结果存入 a3;a3 的值即为 -a1 的补码
andi a3, 0x7 # a3 与 0x7 相与,结果存入 a3(即判断a0的低三位是111 或 000,若为 000 则a3 为 0,否则为 1)
beq a3, zero, shft1 # a3 等于 0(即a0 对齐) 则跳转至 shft1
PTR_SUBU a2, a3 # a2 = a2 - a3;a3 = 1(此指令位于延迟槽中);
LDHI t0, 0(a1)
LDLO t0, 7(a1) #
PTR_ADDU a1, a3
SDHI t0, 0(a0)
PTR_ADDU a0, a3
shft1: # 此处a0 是已对齐了,a1 未知是否对齐
andi t0, a2, 0x7 # t0 = a2 & 0x7
PTR_SUBU a3, a2, t0 # a3 = a2 - t0 = a2 - a2 % 8
PTR_ADDU a3, a1 # a3 = a3 + a1 ;用作下面循环是否结束的判断对比值
shfth:
LDHI t1, 0(a1) # a1 未对齐,使用非对齐指令将从a1 的地址数据load 到 t1 的右部(因为2F是小端,如果是大端则是左端)
LDLO t1, 7(a1) # 取从a1 所在的对齐地址(eg:假设a1 从 0x1002存放(存放地址为:0x1002--0x100a),则其到对齐的地址为 0x1008)到 addr(a1) + len - 1 (即 从 0x1002+8-1 = 0x1009处开始取数据)处的部分数据(len 为数据单元长度,半字为2, 双字为8),置入寄存器左部(小端)
PTR_ADDIU a0, 8
PTR_ADDIU a1, 8
bne a1, a3, shfth # a1 不等于 a3 跳转至 shfth
sd t1, -8(a0)
b last16 # 处理小于 8 的字节的余数
move a2, t0 # a2 = t0 = a2 % 8; 即把 a2 除去 8 后的得到的余数留作后面处理;此指令位于延迟槽中
.set reorder # 打开指令自动排序模式
.end godson2e_memcpy # 标记函数 godson2e_memcpy 的结束位置
/* END (godson2e_memcpy) */
测试程序(mtest.c):
/*
* Writtern by comcat
*
*/
char src[127];
char dest[127];
void init_src()
{
int i;
for(i = 0; i < 126; i++)
src[i] = i+1;
}
void output(int beg, int end)
{
int i;
for(i = beg-1; i < end; i++)
printf(" %d, ", dest[i]);
printf("\n");
}
void test_last16()
{
godson2e_memcpy(dest, src, 14);
output(1, 19);
}
int main()
{
init_src();
test_last16();
return 0;
}
编译:
gcc mtest.c god_memcpy.S -o mtest
备注:如果编译时: gcc god_memcpy.S mtest.c -o mtest 有时出产生不可意料的结果,具体原因还未弄明白
阅读(1810) | 评论(0) | 转发(0) |