我正在尝试用ARM汇编语言实现展位的乘法算法。
Algorithm 2: Booth’s Algorithm to multiply two 32-bit numbers to produce a 64-bit result
Data: Multiplier in V , U = 0, Multiplicand in N
Result: The lower 64 bits of UV contain the result
1 i←0
2 prevBit ← 0
3 fori<32do
4 i←i+1
5 currBit ← LSB of V
6 if (currBit,prevBit) = (1,0) then
7 U←U−N
8 end
9 else if (currBit,prevBit) = (0,1) then
10 U←U+N
11 end
12 prevBit ← currBit
13 UV ← UV ≫ 1 (arithmetic right shift)
14 end
如何执行算法的第 13 步?如何对作为 32 位部分存储在两个寄存器上的 64 位数字执行 asr?
我尝试在两个寄存器上执行 asr,然后对于低 32 位,我将 MSB 替换为高 32 位的 LSB(在执行 asr 之前存储)。
询问编译器:
int64_t asr_1(int64_t a){ return a>>1; }
。在标准调用约定中,第一个参数和返回值都在 R1:R0 中,因此编译器将使 asm 就地运行。 对于移位计数为 1 的情况,Clang 使用进位标志来获取从高半部分底部到低半部分顶部的位。
// clang -O2 -Wall -mcpu=cortex-a77
asrs r1, r1, #1 @ set flags, including C from the bit shifted out
rrx r0, r0 @ rotate-through-carry, shifting C into the top
GCC 不使用进位标志,使用与它和 clang 用于 2 到 31 之间的移位计数相同的策略。
int64_t
的低半部分是无符号的; 逻辑移位在某些地方可以进行 OR 运算时留下零。来自高半部分的位。对于 rrx
计数,此策略的效率低于 1
技巧,除非 rrx
在某些 CPU 上速度很慢。
// return a>>5 with clang; GCC is similar but uses a MOV to copy R1 and OR last
lsr r0, r0, #5 @ lo >>= 5
orr r0, r0, r1, lsl #27 @ lo|=high<<(32-5) to shift bits between them
asr r1, r1, #5 @ hi >>= 5
Clang 与
-mthumb
奇怪地在 asrs.w
之前使用 rrx
,但对于 asrs
仍然是简单的 return a>>(32+5);
(16 位),这很简单: asrs r0, r1, #5
; asrs r1, r1, #31
(根据符号位,返回值的高半部分要么全0,要么全1。)
AFAICT 没有使用
asrs.w
的正确性理由。 asrs
可编码为 16 位 Thumb 指令,尽管 rrx
仅适用于 Thumb 2。这不需要 ARMv8 或任何东西,事实上 clang -march=armv4t
仍然使用它;我只是倾向于使用 -mcpu=cortex-a77
或 a53
因为它是最近出现的并且很容易想到,我想知道是否有任何新的技巧,以及适合现代 ARM 内核的调整选择。 cortex-m3
或 m0
也与某些项目相关。 M0 特别缺乏大多数 Thumb 2 编码:
// GCC or Clang -mcpu=cortex-m0
lsls r2, r1, #31 // extract the low bit of the high half
lsrs r0, r0, #1
adds r0, r0, r2 // and OR it in to lo >> 1
asrs r1, r1, #1 // hi >>= 1
变量计数移位更困难,但编译器仍然可以使用谓词执行向您展示如何操作。 GCC
-marm
是其中或 clang 的 int64_t asr(int64_t a, int c){ return a>>c; }
版本中最短的
// GCC -O3 -mcpu=cortex-a77 for a variable-count shift. Count in R2
lsr r0, r0, r2
rsb r3, r2, #32 // 32-count
subs ip, r2, #32 // count-32 and set flags
orr r0, r0, r1, lsl r3
orrpl r0, r0, r1, asr ip // ORR if PLus (if Negative flag == 0)
asr r1, r1, r2
bx lr
ARM 移位计数 >= 32 移出所有位,为逻辑移位产生零。因此,我认为,如果
r1, lsl r3
源操作数实际上是我们需要的其他位,则它为零。 asr r1, r1, r2
设置高半部分的效果类似于 r2==32 或更高的 asr r1, #31
;它仍然是原始输入移位计数。