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的值为 0x8000 0012,则此时它在$16中的全值为0x0000 0000 8000 0012,而按照运行于32位兼容模式下的定义,低32位的值应该高位符号扩展的,$16中的值是这样 0xffff ffff 8000 0012 才表示为负数。故而在下面 bgez 判断的时候就会出现差错。

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

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

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


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



 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值