32位和16位指令集模式自动切换机制
Posted 吴跃前
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了32位和16位指令集模式自动切换机制相关的知识,希望对你有一定的参考价值。
我们都知道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
以上是关于32位和16位指令集模式自动切换机制的主要内容,如果未能解决你的问题,请参考以下文章