返回列表 发帖

32位模式下使用64位寄存器注意事项

1. 汇编环境

龙芯2E平台32位OS模式下,要使用64位寄存器可以在汇编代码里直接用,运算时使用d开头的指令(double-word, 64bit),作用于寄存器即可。如:dadd, dsub, dmult, dmultu,ddiv, dsll, dsrl, dsra 等等。

访问存储器可以直接使用ld/sd, ldc1/sdc1

使用这些指令前,先用伪操作 .set mips3 告诉汇编器下面的指令是MIPS IV(64位指令集,兼容32位指令)中的指令。


2. C语言环境

可以内嵌汇编,在汇编中使用dadd, dsub, dmult, dmultu, ddiv, dsll, dsrl, dsra 等指令

内嵌汇编与C语言之间的数据方式要特别注意,如读取CP0 25 寄存器(64位,高低32位各为一个计数器)的值,使用如下代码,获取的高32位的值是不正确的:

    long long counter;
    int c0, c1;

    asm(
            ".set mips3\n\t"
            "dmfc0 %0, $25\n\t"
            ".set mips1\n\t"
            : "=r" (counter)
       );

    c0 = counter;
    c1 = counter >> 32;

    printf("low is 0x%x\n", c0);
    printf("high is 0x%x\n", c1);


可以与如下代码获取的值比较下就知道了:

unsigned int counter0, counter1;
unsigned int control;

void read_pmc()
{
    __asm__ volatile (
            ".set mips3     \n\t"
            "dmfc0 %0, $24  \n\t"
            ".set mips0     \n\t"
            :"=r"(control)
            );

    __asm__ volatile (
            ".set mips3     \n\t"
            "dmfc0 %0, $25  \n\t"
            ".set mips0     \n\t"
            :"=r"(counter0)
            );

    __asm__ volatile (
            ".set mips3     \n\t"
            "dmfc0 $8, $25  \n\t"
            "dsrl %0, $8, 32 \n\t"
            ".set mips0     \n\t"
            :"=r"(counter1)
            );
}


int main()
{
    read_pmc();

    printf("control register is(cp0_24): 0x%016x\n", control);
    printf("counter0 register is(cp0_25_low): 0x%08x\n", counter0);
    printf("counter1 register is(cp0_25_high): 0x%08x\n", counter1);

}


如果一定要把CP0_25的值放在一个long long 类型的变量里,则:

    long long counter;
    int c0, c1;

    asm(
        ".set mips3\n\t"
        "dmfc0 %M0, $25\n\t"
        "dsll   %L0, %M0, 32\n\t"
        "dsrl   %M0, %M0, 32\n\t"
        "dsrl   %L0, %L0, 32\n\t"
        ".set mips0\n\t"
        : "=r" (counter)
    );

    c0 = counter;
    c1 = counter >> 32;

    printf("low is 0x%x\n", c0);
    printf("high is 0x%x\n", c1);


同样的要将一个long long的值写入,需用如下代码方能正确写入:

  asm(
        ".set mips3\n\t"
        "dsll   %L0, %L0, 32\n\t"
        "dsrl   %L0, %L0, 32\n\t"
        "dsll   %M0, %M0, 32\n\t"
        "or   %L0, %L0, %M0\n\t"
        "dmtc0 %L0, $25\n\t"
        ".set mips0\n\t"
        ::"r" (counter)
    );

其中counter得定义为 long long

如果要用上面的代码写入0,则一定要这样 long long counter = 0 才可,直接传常数给他是不行的。


3. 常见问题

试看如下代码:

1    long long counter;
2    int c0, c1;

3    asm(
4        ".set mips3\n\t"
5        "dmfc0 %M0, $25\n\t"
6        "dsll   %L0, %M0, 32\n\t"
7        "dsrl   %M0, %M0, 32\n\t"
8        "dsrl   %L0, %L0, 32\n\t"
9        ".set mips0\n\t"
10        : "=r" (counter)
11    );

12    c0 = counter;
13    c1 = counter >> 32;

14    if(c0 < 0)
15        printf("low is 0x%x\n", c0);

16    if(c1 < 0)
17        printf("high is 0x%x\n", c1);


其意图是在任何一个计数器溢出时(最高位为1,则补码表示即为负数),打印其值。

gcc 不带优化选项 -O 编译之,运行是没有问题的。

倘若加-O 编译之,运行时并非如我们所期待的行为,最高位为1时,他也不打印,逻辑关系完全错了。



这个问题我们来对比下gcc 生成的汇编码就清楚了:

不加-O 选项生成的代码为:

    .set mips3
    dmfc0 $3, $25
    dsll   $2, $3, 32
    dsrl   $3, $3, 32
    dsrl   $2, $2, 32
    .set mips0

    sw  $2,32($fp)
    sw  $3,36($fp)
    lw  $2,32($fp)

    sw  $2,28($fp)
    lw  $4,36($fp)
    lw  $5,36($fp)

    sra $2,$4,0
    sra $3,$5,31

    sw  $2,24($fp)
    lw  $2,28($fp)

    bgez    $2,$L2
    lw  $2,%got($LC0)($28)

    addiu   $4,$2,%lo($LC0)
    lw  $5,28($fp)
    lw  $25,%call16(printf)($28)

    jalr    $25
    lw  $28,16($fp)
    .....

加 -O 选项生成的代码为:

    .set mips3
    dmfc0 $17, $25
    dsll   $16, $17, 32
    dsrl   $17, $17, 32
    dsrl   $16, $16, 32
    .set mips0

    bgez    $16,$L2
    lw  $4,%got($LC0)($28)

    addiu   $4,$4,%lo($LC0)
    lw  $25,%call16(printf)($28)
    .set    noreorder
    .set    nomacro
    jalr    $25
    move    $5,$16
    .set    macro
    .set    reorder
   .....


可以看到使用 -O 时,gcc 将原两条 sra 指令合并入 dsll-dsrl-dsrl 中了,那么由dsrl 作用得到的counter0(对应$16)、counter1 (对应$17),其高32位是都为0的,如果counter0的值为 0x80000012,则此时它在$16中的全值为0x0000 0000 80000012,而按照运行于32位兼容模式下的定义,低32位的值应该高位符号扩展的,$16中的值是这样 0xffff ffff 8000 0012才表示为负数。故而在下面 bgez 判断的时候就会出现差错。

从这一点可以看到,分支转移指令的作用范围还是整个64位的。

解决的方法是将嵌入的汇编中的 dsrl 换成 dsra。

代码中的这种问题极其隐蔽,且很难发现,需要谨慎在意才是。


另外,内核中 include/asm-mips/mipsregs.h 中定义的__read_64bit_c0_split 宏就有这个问题,使用时要小心在意,因为内核编译时,是打开 -O 选项的。
重要的是永远的具有活力,而不是永远的活着

返回列表