Sik*_*ato 2 assembly arm thumb parity
我发现汇编语言存在一些困难,不幸的是,当我在谷歌上搜索信息时,我找不到任何可以帮助我解决问题的东西。我已经编写了这段代码,我正在寻求帮助,看看是否有办法使它更简单(如果可能的话)。另外,如果评论有误,请告诉我。
NAME main
PUBLIC main
SECTION .text: CODE (2)
THUMB
main
LDR R4, =0x0097 ; R4 = 97 in hex
BL SUBROUTINE ; Go to Subroutine
STOP B STOP
SUBROUTINE
MOV R1, #1 ; Initialize R1 to 1
MOV R2, #0 ; Initialize R2 to 0
MOV R0, #0 ; Initialize R0 to 0
PUSH {R4}
LOOP
CMP R0, #8 ; Bits counter
BEQ DONE ; Go to DONE R0 = 8
ADD R0, R0, #1 ; Calculates the bits
AND R3, R4, R1 ; Checks if R3 = R4
CMP R3, #1 ; Comparing result with 1
BEQ ONE ; Jump to ONE
LSR R4, R4, #1 ; Right shift by 1
B LOOP
ONE
ADD R6, R6, #1 ; Saving #1 in R6
LSR R4, R4, #1 ; Right shift by 1
B LOOP
RETURN0
MOV R2, #0
POP {R4}
B STOP
RETURN1
MOV R2, #1
POP {R4}
B STOP
DONE
CMP R6, #2
BEQ RETURN0
CMP R6, #4
BEQ RETURN0
CMP R6, #6
BEQ RETURN0
CMP R8, #8
BEQ RETURN0
B RETURN1
END
Run Code Online (Sandbox Code Playgroud)
任务如下:子例程在寄存器 R4 中具有输入参数,并在寄存器 R2 中传递返回值。子程序将检查输入参数的 8 个最低有效位的奇偶校验。如果奇偶校验为偶数,则返回值 0,如果奇偶校验为奇数,则返回值 1。偶校验是指个数为偶数,奇校验是指个数为奇数。
提前致谢
你的编程风格已经相当不错了,你对你的代码进行了彻底的注释。这是非常有价值的,你应该继续做的事情。该算法本身似乎是正确的,并且以可接受的方式实现,尽管它可以更有效地完成。
我一直在假设您正在 ARM 模式下编程的情况下写这个答案。然而,大部分建议也适用于 Thumb 模式。我想你不能使用 Thumb 2 指令。针对拇指的建议以倾斜字体标注。
编写高效的汇编代码时最重要的是了解您正在编程的体系结构的指令集。您的代码是为 ARM 编写的,它具有许多有用的指令和功能来加快速度。让我们从一些基本的改进开始。
首先,您使用此序列来隔离 的最低有效位R4
,然后检查它是否非零:
ADD R0, R0, #1 ; Calculates the bits
AND R3, R4, R1 ; Checks if R3 = R4
CMP R3, #1 ; Comparing result with 1
BEQ ONE ; Jump to ONE
Run Code Online (Sandbox Code Playgroud)
这可以更有效地完成。首先,请注意,您可以在AND
指令中使用立即数,因此无需为此在寄存器中保留 1:
AND R3, R4, #1
Run Code Online (Sandbox Code Playgroud)
接下来,您可以告诉处理器直接根据指令的结果设置标志,而不是将按位的结果AND
与进行比较。如果结果为零(可能还有其他一些标志,不要太在意),这会设置零标志,因此您可以立即对结果进行分支。#1
AND
ANDS R3, R4, #1 ; check if least significant bit set in R4
BNE ONE ; jump to ONE if it is
Run Code Online (Sandbox Code Playgroud)
现在这ANDS
完成了工作,但不必要地将其结果写入R3
. 我们在那里并不真正需要它。快速查看指令集参考告诉我们,它TST
执行相同的操作,ANDS
但丢弃结果,仅设置标志。这正是我们想要的。
TST R4, #1 ; check if least signficant bit set in R4
BNE ONE ; jump to ONE if it is
Run Code Online (Sandbox Code Playgroud)
现在我们要做的下一件事就是摆脱那个条件分支。分支中的代码之间的唯一区别ONE
在于它是递增的R6
。我们可以简单地使用 ARM 的条件执行ADD
功能来仅在设置零标志时执行指令,而不是条件分支:
TST R4, #1 ; check if least significant bit set in R4
ADDNE R6, R6, #1 ; increment R6 if it is
Run Code Online (Sandbox Code Playgroud)
这使得代码更加高效!TST
我们可以通过将其合并到指令中来进一步改进LSR
。看,如果我们告诉LSR
设置标志,它将进位标志设置为移出的最后一位。这正是我们感兴趣的!所以我们可以这样做
LSRS R4, R4, #1 ; shift R4 to the right and set flags
ADDCS R6, R6, #1 ; increment R6 if a 1 was shifted out
Run Code Online (Sandbox Code Playgroud)
请注意,在条件执行不可用的其他体系结构上,您可以达到与ADDCS R6, R6, #1
使用 add-with-carry 指令类似的效果:
ADC R6, R6, #0 ; add 1 to R6 if carry is set
Run Code Online (Sandbox Code Playgroud)
这也是我在拇指模式下会做的事情。由于拇指模式下没有立即操作数ADC
,因此您必须将一个寄存器设置为零。
MOVS R1, #0
...
LSRS R4, R4, #1
ADCS R6, R1, #0 ; add carry to R6
Run Code Online (Sandbox Code Playgroud)
除了设置进位标志之外,LSRS
如果结果为零,还设置零标志。因此,如果我们简单地迭代直到所有位都被移出,我们就可以取消循环计数器R4
,从而节省一个寄存器和一堆指令。请注意,如果在 中设置了任何额外的位(除了您检查的至少 8 位之外)R4
,这可能不会产生正确的结果,因此您可能需要首先屏蔽这些位AND R4, R4, #0xff
。这是代码:
LOOP: LSRS R4, R4, #1 ; shift R4 to the right and set flags
ADDCS R6, R6, #1 ; increment R6 if a 1 was shifted out
BNE LOOP ; loop until R4 is 0.
Run Code Online (Sandbox Code Playgroud)
不幸的是,所有拇指指令都会设置标志,因此您无法进行此优化。
您可以类似地优化该部分中的代码DONE
:本质上,您只需检查是R6
偶数还是奇数,然后返回1
奇数或0
偶数。您可以用单个测试替换整个级联的跳转:
TST R6, #1 ; set the zero flag if R6 is even
BEQ RETURN0 ; return 0 if even
B RETURN1 ; otherwise return 1
Run Code Online (Sandbox Code Playgroud)
但是,然后,意识到这基本上与返回 的最低有效位相同R6
,因此您可以将整个代码替换为
AND R0, R6, #1 ; set R0 to 1 if R6 is odd, 0 if R6 is even
POP {R4}
B STOP
Run Code Online (Sandbox Code Playgroud)
这有点短,不是吗?
在拇指代码中,通过一些巧妙的思考可以实现类似的性能。请注意,我们只关心最低有效位R6
,而丢弃高位并不重要。因此我们可以写
MOVS R0, #0 ; parity accumulator
SUBS R1, R0, #2 ; mask (clear in bit 0, 1 everywhere else)
LOOP: LSRS R4, R4, #1 ; shift out one bit from R4 and set flags
ADCS R0, R0, R1 ; add that bit to R0
CMP R4, #0 ; are we done?
BNE LOOP ; loop until we are
BICS R0, R1 ; isolate parity
Run Code Online (Sandbox Code Playgroud)
然后可以在 中找到结果R0
。
现在进行一些算法改进:您的代码可以实现这一点,但它确实相当慢,因为它对每个数字进行一次迭代。更快的方法是使用指令将位压缩在一起XOR
。这使我们只需 3 个步骤即可计算奇偶校验,而不是像您的代码那样需要 8 个步骤:
LSR R3, R6, #4 ; keep a copy of R6 shifted by 4 places
EOR R6, R6, R3 ; and xor it into R6
LSR R3, R6, #2
EOR R6, R6, R3 ; same but shifted by 2 places
LSR R3, R6, #1
EOR R6, R6, R3 ; same but shifted by 1 place
AND R0, R6, #1 ; isolate parity
Run Code Online (Sandbox Code Playgroud)
可以在拇指模式下编写相同的代码,但您可能需要在两者之间移动一些额外的数据。
这可以使用移位操作数(另一个 ARM 特定功能)进一步改进:
EOR R6, R6, R6, LSR #4 ; xor R6 with R6 shifted right 4 places
EOR R6, R6, R6, LSR #2 ; xor R6 with R6 shifted right 2 places
EOR R6, R6, R6, LSR #1 ; xor R6 with R6 shifted right 1 place
AND R0, R6, #1 ; isolate parity
Run Code Online (Sandbox Code Playgroud)
这通常是最快的方法,无需使用任何指令集扩展。如果您有足够先进的处理器,则可以使用该CNT
指令一步计算位,但无论如何,这并不值得在这里付出努力。