【发布时间】:2019-12-05 04:17:10
【问题描述】:
我的任务是在 8085 汇编语言中找到任何给定数字的绝对值。
算法如下(在互联网上找到):
mask = n >> 7(数字本身是8位)
(掩码 + n) 异或掩码
我的问题是如何用汇编语言实现它。 看来我应该使用“RRC”命令,但是对数字执行循环移位,算法似乎不起作用。
任何想法将不胜感激。 干杯。
【问题讨论】:
标签: assembly bit-manipulation bit-shift 8085
我的任务是在 8085 汇编语言中找到任何给定数字的绝对值。
算法如下(在互联网上找到):
mask = n >> 7(数字本身是8位)
(掩码 + n) 异或掩码
我的问题是如何用汇编语言实现它。 看来我应该使用“RRC”命令,但是对数字执行循环移位,算法似乎不起作用。
任何想法将不胜感激。 干杯。
【问题讨论】:
标签: assembly bit-manipulation bit-shift 8085
abs 算法中的n>>7 是一个算术右移,它会在符号位的副本中移动,所以你得到-1 用于负n,0 用于非消极的。 (在 2 的补码中,-1 的位模式已设置所有位)。
然后你用它要么什么都不做(n+0) ^ 0,要么用-n = (n + (-1)) ^ -1 = ~(n-1)“手动”做2的补码否定。
请参阅How to prove that the C statement -x, ~x+1, and ~(x-1) yield the same results? 了解 2 的补码恒等式。与全一的异或是按位非。添加mask = -1当然是n-1
分支机构很便宜,而且创建和使用0 或-1(根据数字符号)所涉及的寄存器复制加起来。 (虽然我确实想出了一种方法,只用 6 字节的代码来实现这一点,与分支版本的代码大小相同。)
在 8085 上,只需简单的方式实现:if(n<0) n=-n;
(将结果视为无符号;注意 -0x80 = 0x80 为 8 位。如果您假设它在 abs 之后是有符号正数,那么对于最负数的输入,您将是错误的。)
这对于否定条件分支的条件分支应该是微不足道的; 8085 确实有依赖于符号位的分支。 (一般不进行签名比较,除非您使用未记录的 k 标志 = 签名溢出)。 根据A 设置标志,然后JP 否定。 (“加号”条件测试 Sign 标志 = 0,因此它实际上是在测试非负而不是严格正)
我在https://www.daenotes.com/electronics/digital-electronics/instruction-set-intel-8085 中没有看到neg 指令,因此您可以将另一个寄存器和sub 归零,或者您可以使用像CMA (NOT A)这样的2 补码标识来否定累加器) ; inr a (accumulator += 1) 而不是 mov 到另一个 reg 并从 A=0 中减去。
8085 具有廉价的分支,不像现代流水线 CPU,在分支错误预测时分支可能代价高昂。 mask = n >> 31 或无分支 abs 的等效项在那里很有用,整个事情通常只有 3 或 4 条指令。 (8085 仅具有移位 1 指令;包括现代 x86 在内的后来的 ISA 具有快速立即移位,可以在单个指令中执行 n >> 31,通常具有良好的延迟,例如 1 个周期。)
; total 6 bytes. (jumps are opcode + 16-bit absolute target address)
ana A ; set flags from A&A
jp non_negative ; jump if MSB was clear
cma
inr A ; A = ~A+1 = -A
non_negative:
; unsigned A = abs(signed A) at this point
http://pastraiser.com/cpu/i8085/i8085_opcodes.html 有一个带有循环时序的操作码映射。 1 字节 ALU 寄存器指令需要 4 个周期,2 字节 ALU reg 指令(带立即数)需要 7 个周期。条件分支需要 7 个周期未占用,10 个周期占用。
(时序计算似乎微不足道;每条指令只有一个固定成本,这与现代流水线超标量乱序 CPU 不同,后者的吞吐量和延迟是独立的,并非每条指令都可以在每个执行端口上运行...... )
SBB A 根据 CF 设置 A = 0 或 -1这是一个众所周知的汇编技巧,用于将比较条件转换为 0 / -1 掩码。您只需要将值的 MSB 放入进位标志,例如用 A+A 或旋转。这为您提供了 xor/add 所需的 n >> 7 0 : -1 值。
只是为了好玩,我尝试用这个技巧无分支地实现 abs()。这是我想出的最好的。 仅当您需要对时序攻击免疫时才使用此选项,因此时钟周期成本不取决于输入数据。(或者对于与位置无关的代码;跳转使用绝对目标地址,而不是 +- 相对偏移量。)
它的优点是可以将原件保存在另一个寄存器中。
;;; UNTESTED slower branchless abs
;; a = abs(b). destroys c (or pick any other tmp reg)
;; these are all 1-byte instructions (4 cycles each)
mov a, b
add a ; CF = sign bit
sbb a ; A = n-n-CF = -CF. 0 or -1
mov c, a
xra b ; n or ~n
sub a, c ; n-0 = n or ~n-(-1) = ~n+1 = -n
; uint8_t A = abs(int8_t B)
这仍然只有 6 个字节,和 branchy 一样,但需要 6*4 = 24 个周期。
如果 XRA 不影响标志,我们可以 sbi 0 执行 -1 步骤。但它确实总是清除 CF。我看不到保存 0 / -1 结果副本的方法。而且我们无法计算到B 就地进行; 8085是一种蓄能器机器。当您需要时,8086 的 1 字节交换与累加器在哪里? xchg a,b 会很有用。
如果你的值从A开始,你需要把它复制到别的地方,所以你需要销毁两个其他寄存器。
将 A 的符号位广播到所有位置的更糟糕的选择:
RLC ; low bit of accumulator = previous sign bit
CMA ; Bitwise NOT: 0 for negative, 1 for non-negative
ANI 1 ; isolate it, clearing higher bits
DCR A ; 0 or 1 -> -1 or 0
这比rlc/sbb a还要糟糕;我仅将它作为位操作的练习包含在内,以了解它为什么起作用。 (而且因为我在记住我从其他 ISA 知道的 SBB 技巧也可以在这里使用之前,已经输入了它。)
【讨论】: