我们都知道MIPS架构体系是32位精简指令集(MIPS32),其实MIPS在进入控制器市场时还推出了MIPS16e指令集模式。号称能够使编译后的代码降低30%左右,相似于ARM架构中是arm32指令(32位)和thumb(16位)指令。内存资源紧缺型系统通常会使用MIPS16e模式进行编译,以缩减内存使用量。达到降低成本的目的。
ISAMODE是指令集执行模式的指示位,但这个比特位并非固定在某个寄存器中。
调试时能够发现,该比特位相应32位执行地址的最低比特位。为1时代表此时芯片执行的是MIPS16e模式,0即意味着MIPS32模式。
指令执行过程中。两种指令集的切换是自己主动、无缝地完毕的。在ARM架构中是arm指令和thumb指令切换。
本文说明两种指令模式的切换机制,并用实例来具体说明两者之间是怎样进行切换的。一、必须跑MIPS32指令集模式的场景
这是MIPS体系结构决定的:
1. 中断/异常。CPU响应中断/异常之后就自己主动进入mips32模式。
2. “sync”指令必须在mips32模式下使用。
3. 协处理器cp0的读写必须在mips32模式下。
4. 由于性能的考虑,某些算法有时追求高效率,应该执行在mips32模式下。
二、执行模式切换
1. 异常/中断
MIPS异常和中断发生时会自己主动转到32位模式。这时EPC寄存器记录的是发生异常时的地址(该地址假设之前执行于MIPS16e模式,则是16位对齐,否则是32位对齐),其最低位就相应ISAMODE bit。中断/异常处理过程能够保存EPC并切换到16e模式执行。
在中断/异常返回时,即调用ERET指令时,能够返回到EPC中保存的地址。并把EPC的最低位赋值给ISAMODE。恢复到原来的执行模式。2. 函数调用和返回
执行模式协同工作的原理主要是在调用时将代表当前执行模式的ISAMODE BIT和返回地址一起放到RA或者某个指定的寄存器。
由于mips16e/32的返回地址至少是2字节对齐。所以其最低位一定是0。ISA MODE BIT占用bit 0不会造成返回地址混乱。
在返回时。依据RA或者RX的bit 0恢复执行模式,接着返回。
1)JAL
这条指令调用后,代码执行模式不变,并且其是在256M的范围内进行调用。在相同的编译模式下。调用函数时会产生JAL指令。
2)JALX
这条指令调用后。代码执行模式反转,相同是在256M的范围内进行调用。
假设在16e的函数中调用32的函数,编译器就会产生JALX调用。相同,假设在32的函数中调用16e的函数,会产生JALX。
3)JALR
这条指令能够调用不同的执行模式的代码,其能够在4G的范围内进行调用。
RX保护目标地址的执行模式和地址内容。
4) JR
利用这条指令返回。依据RA或者RX的bit0恢复执行模式,接着返回。
3. 使用总结
1) 尽管是用MIPS16E编译,但代码中相同会出现32位扩展指令,上面这些跳转指令和save 、restore等指令都是32位编码的。CPU执行在16位模式时。假设碰到这些指令。也是先取16bit内容。但译码时发现其是32位指令,即停止译码,并把后面16bit取进来一起执行。
其实16e模式的16bit指令在CPU内部一样会翻译为32bit指令。
2) 开发者应该明白自己负责开发的代码以何种模式编译,我们有两种选择决定函数的编译模式。
一种是命令行选项,一种是加入函数的属性。
编译选项例如以下:
GCC_MIPS16e = -g -c -G0 -Wall-Wno-unknown-pragmas $(INCLUDE) -msoft-float -fsigned-char -mtune=m4k-mips16e-nostdinc -nostdlib -fno-builtin $(OFORMAT) $(BUILD_MACRO)
GCC_MIPS32 = -g -c -G0 -Wall-Wno-unknown-pragmas $(INCLUDE) -msoft-float -fsigned-char -mtune=m4k -mips32r2-nostdinc -nostdlib -fno-builtin $(OFORMAT) $(BUILD_MACRO)
定义函数时加入属性声明__attribute__((mips16))是告诉编译器以mips16e的模式去编译该函数。__attribute__((nomips16))即表示以mips32的方式去编译。对于C文件,我们一般用命令行选项去决定其使用何种模式进行编译。但假设在一个文件里某个函数希望以另外的模式编译时就要加入函数的属性。这时命令行的编译模式选项是不起作用的。
汇编文件头部一定要注明这个编译属性。
Gcc命令行选项的mips16/mips32对汇编文件不起作用。
3) 在调用外部函数时。一般要extern声明被调用的函数,这时不需说明该函数的编译属性。说明了也不起作用。链接阶段会依据函数定义时的编译属性生成相应的调用指令。
三、例证
不管是同种模式的调用还是不同模式之间的调用。在256M空间内(默觉得near)一律编译出来为jal。在链接阶段才确定是否须要改动为jalx。在超过256M的空间内(声明被调用函数为far)一律编译出来是jalr或者jalrc,在链接阶段才确定是否要改动指令。
1. mips32模式函数调用(mips16e或者mips32)模式函数
1) 使用命令行选项CC_OPTS_BASE进行编译。
2) 举例代码:
//该函数在不同的文件里定义。链接地址在256M范围内,并以mips16e模式编译。
extern int test_16_fun_near(int a);
//该函数在不同的文件里定义,链接地址超过256M范围内,并以mips16e模式编译。
extern int test_16_fun_far(int a) __attribute__((far));
//该函数在本文件里定义,但以mips16e模式编译。
int __attribute__((mips16))test_16_fun_inline(int a);
//该函数在本文件里定义。并以mips32编译。
int test_32_fun_inline(int a);
//调用函数演示样例
int test_32_fun(int a)
{
int b,c,d,e;
b= test_32_fun_inline(88);
d= test_16_fun_inline(77);
c= test_16_fun_near(99);
e= test_16_fun_far(66);
return a + 100 + b + d;
}
3) 编译及链接例如以下:
可见在编译阶段都是产生jal和jalr 指令,在链接阶段才修正相关指令。
调用test_32_fun_inline终于生成jal指令。ISA MODE不变。
调用test_16_fun_inline终于生成jalx指令,ISA MODE反转。
调用test_16_fun_near终于生成jalx指令。ISA MODE反转
调用test_16_fun_far终于生成jalr指令,v0寄存器的值是
0x80000000+0x61(97是十进制)= 0x80000061,而test_16_fun_far的实际地址是0x80000060。bit0代表test_16_fun_far执行的是1,即mips16e的模式。
2. mips16e模式函数调用(mips16e或者mips32)模式函数
1)使用命令行选项CC_OPTS_BASE_16进行编译.
2)举例代码:
//另外文件定义,32编译,超过256M。
extern int test_32_fun_far(int a)__attribute__((far));
//另外文件定义,32编译。256M范围内。
extern int test_32_fun_near(int a);
//本文件定义,但以32编译。
int __attribute__((nomips16))test_32_fun_inline_1(int a) ;
//本文定义,16e编译。
int test_16_fun_inline(int a);
//调用演示样例代码:
int test_16_main(int a)
{
int b,c,d,e;
b= test_16_fun_inline(88);
d= test_32_fun_inline_1(77);
c= test_32_fun_near(99);
e= test_32_fun_far(66);
return a + 100 + b + d;
}
3) 编译和链接例如以下:可见在编译阶段都是产生jal和jalrc 指令。在链接阶段才修正相关指令。
调用test_16_fun_inline终于生成jal指令,ISA MODE不变。
调用test_32_fun_inline_1终于生成jalx指令,ISA MODE反转。
调用test_32_fun_near终于生成jalx指令,ISA MODE反转
调用test_32_fun_far终于生成jalrc。其寄存器所放的值是间接跳转的。这条指令是依据相应的寄存器的值 来进行跳转的。
v0的值是0x80000284内存的值,即0x80000168。其bit0是0,所以jalrc到该地址后。其ISA MODE 为0,即从16e转变为32模式。
对间接跳转。我们进行特别的分析:
mips32调用mips16e:调用test_16_fun_far时,jalr v0前的指令是lui v0, 0x8000;
add v0,v0,XX。即对v0赋值是使用lui这样的利用直接数进行赋值的指令。编译阶段是add v0,v0,0;链接阶段 是add v0,v0,97进行了一次重定位。
mips16调用mips32:调用test_32_fun_far时。jalrc v0 前的指令是lw v0, 84 (b208),并且其编译和链接 阶段的指令码都没有变,变的是0x80000284这个内存。编译阶段是全0。链接后是test_32_fun_far的实际地址0x 80000168。
Lw指令跟lui指令不同。其是取内存的值到v0,而不是把地址值直接赋给v0.
为什么在0x80000266的地方的指令码为b208,取的内存却是0x80000284呢?0x80000284 = 0x80000266 & 0xfffffffc0 + 8 << 2