Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1556085
  • 博文数量: 290
  • 博客积分: 3468
  • 博客等级: 中校
  • 技术积分: 3461
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-28 22:21
文章分类

全部博文(290)

文章存档

2016年(13)

2015年(3)

2014年(42)

2013年(67)

2012年(90)

2011年(75)

分类: 嵌入式

2011-12-01 22:24:09

以下程序均是由一个网名: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  有时出产生不可意料的结果,具体原因还未弄明白
   

阅读(1348) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~