如何在程序集中将带符号的8位字节转换为带符号的16位整数?

时间:2016-11-29 06:26:50

标签: assembly arduino int byte avr

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

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

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

有谁知道如何让这个功能起作用?任何帮助将不胜感激!

1 个答案:

答案 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, $FFSBR 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是一个相当简单的有序流水线设计,所以没有办法让一个旧的寄存器成为一个性能的地雷,除非那个(例如)缓存失败的代码从未加载到它使用了结果。 (即使是有序流水线也不需要等待高延迟操作,直到某些东西使用结果。)