如何在汇编中将有符号 8 位字节转换为有符号 16 位整数?



使用 Arduino,我必须在 Atmel AVR 汇编中为我的计算机科学课程编写一个函数,将有符号的 8 位字节转换为有符号的 16 位整数。我也不允许使用任何分支指令(但跳过很好)。

我知道这是错的,但这就是我到目前为止得到的全部:

.global byteToInt
byteToInt:
sbrc r24, 7
ldi r25, 1
asr r25
ret

有谁知道我将如何使此功能工作?任何帮助将不胜感激!

显然,您需要将char的符号位复制到上半部分的每个位。 在大多数架构上,最简单的方法是复制一个寄存器并将其算术右移 7。 但是 AVR 只有 1 班次指令,所以我们无法有效地做到这一点。

有条件地将 0 或 -1 放入寄存器的另一个技巧是从自身减去借用寄存器以获得0 - C。 例如sbc r25, r25.

现在我们只需要一种方法来设置 Carry 标志,如果 8 位数字为负数,即当它被视为无符号整数时是否> 127,因为 C 总是基于事物的无符号解释设置的。 AVR 有一个比较即时指令 CPI,但它仅适用于 r16-r31,不适用于低寄存器。 此外,它将 C 标志设置为与我们真正想要的相反,因此我们必须使用另一条指令来反转结果。 所以我认为我们最好以另一种方式与寄存器中的值进行比较:

; Most efficient way, I think:
sign_extend:
ldi   r25, 127       ; can be hoisted out of loops, and any reg is fine.
cp    r25, r24        ; C = (r24 < 0)
sbc   r25, r25        ; r25 = (r24 < 0) ? -1 : 0
; result in r25:r24

更好的是,如果您需要在循环中执行此操作,则可以将 127 保留在不同的寄存器中。

使用 CPI,您可以执行以下操作:

; slightly worse: only works with r16-r31, and worse in loops
sign_extend:
cpi   r24, 127        ; C = (r24 < 128U) = ((signed)r24 >= 0)
sbc   r25, r25        ; r25 = (r24>=0) ? -1 : 0
com   r25             ; ones-complement negation: 0 : -1

或者,为了避免对使用哪个寄存器的限制,请以另一种方式进行比较:

我从未使用过 AVR,所以我只是基于谷歌找到的指令集参考手册(以及我对其他 ISA 的 asm 的了解,如 x86 和 ARM)。 根据这些文档,所有这些指令都是 1 个单词(2 个字节),具有 1 个周期延迟。 这比 gcc4.5 做的要好:


找到好的指令序列的常用方法是询问编译器 AVR gcc4.5-O3在 godbolt 上这样做:

short sign_extend(signed char a) { return a; }
sign_extend:
mov r18,r24     ;; IDK why gcc uses r18 and r19.
clr r19
sbrc r18,7
com r19
mov r25,r19
ret

因此,它将 R19 归零,然后使用 SBRC 有条件地执行逻辑非 (COM),具体取决于 R18 的符号位(位 7)。

我不确定额外的 MOV 是做什么用的。 我也不确定为什么它会反转零而不是设置没有输入依赖性的所有位。 (例如ldi r19, $FF,或它的 SBR 别名。 如果曾经存在过无序执行 AVR,那么这将更有效率。 :P

我不确定 MOV 说明的用途。 SBRC是非破坏性的。 所以AFAICT,一个有效的实现将是

sign_extend:
clr   r25
sbrc  r24,7
ldi   r25, $FF
ret

这仍然比 CP/SBC 更糟糕,因为如果跳过,SBRC 需要 2 个周期


我认为 SBC 对 R25 旧值的"错误依赖"对 AVR 不是一回事。 在乱序x86 CPU上,只有AMD认为sbb eax, eax独立于eax的旧值,并且仅依赖于标志。 英特尔 CPU 只是正常运行它。 (他们确实将像xor eax,eax这样的指令识别为独立的,这是x86的标准归零习惯用语。

因此,在非 AMD CPU 上,如果编写 EAX 的最后一个代码在缓存中丢失负载或其他高延迟的情况下执行此操作,则即使标志准备就绪(即来自独立的依赖链),sbb eax, eax也无法执行。 但在AMD CPU上,它将为EAX启动一个新的依赖链。

无论如何,我认为 AVR 是一个相当简单的顺序流水线设计,因此旧寄存器不可能成为性能地雷,除非(例如)加载缓存未命中的代码从未使用过结果。 (即使是按顺序的管道也不需要等待高延迟操作,直到某些内容使用结果。

最新更新