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 选项的。