我們都知道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