使用Arduino,我必须在Atmel AVR Assembly中为我的计算机科学类编写一个函数,将有符号的8位字节转换为带符号的16位整数。我也不被允许使用任何分支指令(但跳过很好)。
我知道这是错的,但这是我到目前为止所得到的:
.global byteToInt
byteToInt:
sbrc r24, 7
ldi r25, 1
asr r25
ret
有谁知道如何让这个功能起作用?任何帮助将不胜感激!
答案 0 :(得分:3)
显然你需要将char
的符号位复制到上半部分的每一位。在大多数体系结构中,直截了当的是复制寄存器并将算术右移它7.但AVR只有shift-by-1 instruction,所以我们无法有效地做到这一点。
有条件地将0或-1加入寄存器的另一个技巧是subtract-with-borrow来自自身的寄存器以获得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,所以我只是根据google发现的指令集参考手册(以及我对其他ISA的asm知识,如x86和ARM)。根据这些文档,所有这些指令都是1个字(2个字节),具有1个周期延迟。这比gcc4.5更好:
找到好的指令序列的常用方法是询问编译器 AVR gcc4.5 -O3
on 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
因此zeros R19,然后使用SBRC有条件地执行逻辑非(COM),具体取决于R18的符号位(第7位)。
我不确定额外的MOV是什么。我也不确定为什么它会反转零而不是设置所有没有输入依赖的位。 (例如ldi r19, $FF
或SBR alias for it。如果存在无序执行的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值,并且仅依赖于标志。 Intel CPU只是正常运行它。 (他们确实将xor eax,eax
等指令视为独立,it's the standard zeroing-idiom for x86。)
因此,在非AMD CPU上,如果编写EAX的最后一个代码使用缓存中遗漏的负载或其他高延迟的代码,即使标志已准备就绪,sbb eax, eax
也无法执行(即来自独立的依赖链)。但是在AMD CPU上,它将为EAX启动一个新的依赖链。
无论如何,我认为AVR是一个相当简单的有序流水线设计,所以没有办法让一个旧的寄存器成为一个性能的地雷,除非那个(例如)缓存失败的代码从未加载到它使用了结果。 (即使是有序流水线也不需要等待高延迟操作,直到某些东西使用结果。)