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

ste*_*r10 5 int assembly byte avr arduino

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

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

.global byteToInt
byteToInt:
  sbrc r24, 7
  ldi r25, 1
  asr r25
  ret
Run Code Online (Sandbox Code Playgroud)

有谁知道我将如何使这个功能工作?任何帮助将非常感激!

Pet*_*des 3

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

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

现在我们只需要一种方法来设置进位标志,如果 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
Run Code Online (Sandbox Code Playgroud)

更好的是,如果您需要在循环中执行此操作,可以将 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
Run Code Online (Sandbox Code Playgroud)

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

我从未使用过 AVR,所以我只是根据 google 找到的指令集参考手册(以及我对其他 ISA 的 asm 的了解,例如 x86 和 ARM)来编写此内容。根据这些文档,所有这些指令都是 1 个字(2 个字节),具有 1 个周期延迟。这比 gcc4.5 所做的更好:


找到好的指令序列的通常方法是要求godbolt 上的AVR gcc4.5编译器 执行以下操作:-O3

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
Run Code Online (Sandbox Code Playgroud)

因此它将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
Run Code Online (Sandbox Code Playgroud)

这仍然比 CP/SBC 差,因为如果采取跳过,SBRC 需要 2 个周期


我认为 SBC 对 R25 旧值的“错误依赖”在 AVR 上不是问题。在乱序 x86 CPU 上,只有 AMD 认为sbb eax, eax与 eax 的旧值无关,并且仅依赖于标志。Intel CPU 就可以正常运行。(它们确实将指令识别xor eax,eax为独立指令,这是 x86 的标准归零习惯用法。)

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

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

  • @stealthbomber10:是的,当我从一种架构(x86)中知道的技巧在另一种架构上有用时(这个答案),这很巧妙。x86甚至有指令[根据标志将寄存器设置为0或1](http://www.felixcloutier.com/x86/SETcc.html),但它们仅适用于8位部分寄存器,并且为0 /1 不是 0/-1。(当然,DEC 指令将 0/1 转换为 -1/0,这是另一个有用的技巧)。我最讨厌的事情之一是 AMD 没有更改 SETCC 以在 64 位模式下写入完整的 32 位寄存器(通常隐式零扩展为 64 位);那样会更有用、更高效。 (2认同)