我目前正在將演算法從 C 重寫為 arm 程式集(ARM Cortex M4 CPU)。
我的代碼有什么作用?
該演算法將一個 8 位數字作為輸入,從右邊開始告訴我們第一個 0 位是什么。這里有幾個例子:
輸入:B01111111 輸出:7
輸入:B01110111 輸出:3
輸入:B11111110 輸出:0
這是完成此操作的原始 C 代碼:
uint8_t find_empty(uint32_t input_word)
{
for (uint8_t searches=7; searches>=0; searches--)
{
if ((input_word&1)==0)
{
return 7-searches;
}
input_word=input_word>>1;
}
return 255;
}
這是我在 ARM (Cortex M4) 程式集中重寫它的初學者嘗試。
.global findEmpty
findEmpty:
mov r1, r0 //Move input_word to r1
//Config
mov r0, #7 //search through 8 (7 1) bits. <-searches
FindLoop:
tst r1, #1 //ANDs input_word with 1, sets the Z flag accordingly.
beq NotFoundYet //didn't get a 0, jump forward
rsb r0, r0, #7 //searches=7-searches <- which bit is 0?
bx lr //Return found bit number
NotFoundYet:
lsr r1, r1, #1 //input_word=input_word>>1
sub r0, r0, #1 //Decrement searches
cmp r0, #0
bpl FindLoop //If searches>=0, do the loop again.
mov r0, #255 //We didn't find anything. Return 255 to signal that
bx lr
快速說明: 我在這里使用 r1 作為變數,我聽說你不應該這樣做,因為編譯器(我正在使用 gcc 將我的程式集“.S”檔案鏈接到 C 檔案)使用 r0-r3 將資料傳遞到和從函式接收資料。但是,正因為如此,它不會將這些暫存器用于重要的事情,所以我不必處理將東西推送到堆疊,這樣可以節省周期。
有什么問題?
當我嘗試編譯我的專案時,gcc 在 TST 行上給了我一個匯編錯誤:
匯編器訊息: 錯誤:需要未移位的暫存器 -- `tst r1, #1'
這讓我很困惑,因為我已經查看了 keil 站點的TST 指令和LSR 指令,我稍后會使用它們將 r1 移位 1。但是他們都沒有說不能一起作業。我在網上尋找有關此主題的其他討論。我遇到了這個討論,人們說告訴編譯器在 ARM 模式下編譯,但我的代碼已經在 ARM 模式下運行,而不是 Thumb。我通過創建另一個 .global 子例程并嘗試將立即超過 7 的數字添加到數字上來確認這一點,果然它不起作用,就像 CPU 處于 ARM 模式時它不應該一樣。
.global illegal_add
illegal_add:
add r0, r0, #20
bx lr
我知道的很少,也不知道如何嘗試解決這個問題。如果有人對嘗試的事情有任何想法,請告訴我。感謝您的幫助。
uj5u.com熱心網友回復:
tst如果您使用正確的匯編程式選項和指令,則有一個 32 位 Thumb2 形式的reg,imm 可以作業。但它對于查找最低 0 或 1 位的位置沒有用。
您只需要三個 T32 指令,每個指令只執行一次(無迭代/無 CPSR):
mvn r0, r0 // binary negate the value
rbit r0, r0 // reverse bit
clz r0, r0 // count leading zeroes
如果沒有找到零,它將回傳 8 而不是 255。你最好撰寫一個行內行內匯編函式,因為這個函式非常小而且很快,函式呼叫開銷將大于函式本身。
正如有人提到的那樣,您只需要聰明,這個特定的人似乎并不聰明。
或者您可以使用內置函式并在 C 中撰寫整個函式:
static inline uint32_t find_empty(uint32_t input_word)
{
input_word = ~input_word;
input_word = __rbit(input_word);
input_word = __clz(input_word);
return input_word;
}
您的工具鏈很可能同時支持rbit和clz,您只需要找到正確的語法。
您甚至可以輕松地將其轉換為x86(更好,因為x86直接“計數尾隨零”,盡管 AMD CPU 將其解碼為 2 uop,可能在內部位反轉以提供 lzcnt,就像我們必須在 ARM 上手動執行的操作一樣):
#include <immintrin.h>
static inline uint32_t find_empty(uint32_t input)
{
input = ~input;
return _tzcnt_u32(input);
}
如果您的編譯器支持GNU 擴展,__builtin_ctz(~input)則可移植地計算尾隨零,在 ARM 上使用 rbit/clz,在 x86 上使用 tzcnt 或 bsf。(但請注意,int如果~input全為零,則結果是一個未定義的值,因為可能使用 x86 bsf。解決此問題的一種方法是(1<<8)|(~input)肯定有一些東西可以找到。)
無論如何,這將帶來巨大的性能提升:
- 沒有 CPSR 依賴/損壞
- 沒有外部依賴
- 無需額外注冊
- 沒有分支
由于上述原因,編譯器將能夠出色地安排這兩個-三個指令。
uj5u.com熱心網友回復:
我不是 100% 清楚問題是什么。很可能,您忘記正確設定程式集。要解決此問題,請在檔案開頭發出這些指令:
.syntax unified
.cpu cortex-m4
.thumb
如果我將這些放在您的代碼前面,它就可以在我的機器上正常組裝。
一些一般提示:
- 閱讀哪些指令是 16 位可編碼的,并嘗試從中挑選指令。16 位指令執行速度更快,記憶體消耗更少。例如,您可以使用
lsrs r1, r1, #1而不是lsr r1, r1, #1獲取 16 位指令。 - 對標志操作更聰明。
tst許多指令已經為您設定了標志,如果您很聰明,您可能會避免所有cmp指令。例如,如果您使用subs r0, r0, #1而不是sub r0, r0, #1保存一個位元組(16 位指令)并且已經根據 設定了 Z 標志,則為您保存r0后續cmp指令。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/464757.html
上一篇:在32位模式下無法寫入顯存
