转移一个庞大的数字 - 集会

时间:2016-12-05 07:42:14

标签: assembly x86 shift

我有一个很大的数字,它被加载到堆栈上,我使用eax访问它。它 不能存储在寄存器中。我只使用eax来指向它的地址(数字是自然类型,这意味着前4个字节包含符号,下一个4表示长度,其他4表示实际值)。

我必须将其edx次移位。 我正在考虑从LSB逐位移位(最多8次/字节),然后将这些位复制到后面的字节中。为了做到这一点,我必须首先移动下一个字节,依此类推,直到MSB位置+ 1(最坏的情况)或者直到所有的移位都没有,并且没有剩余进位标志。附:我显然是在shl这种特殊情况下谈论,但shr几乎相同。

有没有更简单的解决方案?

2 个答案:

答案 0 :(得分:1)

经典的8位时代想法是使用由DEC counter + JNZ交错的RCL(左侧带进位) - 您可以暂停一秒钟并最终欣赏,为什么x86 DEC/INC指令仅影响零标志,但不要携带(神秘解决)。

所以代码将沿着这些方向发展:

    mov   edi,address_of_last_byte
    mov   edx,count_of_bytes
    mov   cl,1
    clc   ; clear CF
loop_1_bit_left:
    rcl   byte [edi],cl    ; CF -> LSB, MSB -> CF
    dec   edi    ; preserves CF! Goes from last byte to first one
    dec   edx    ; preserves CF! Decrement counter
    jnz   loop_1_bit_left  ; till whole buffer is shifted
    ; CF has last bit, will be thrown away unless you do something about it

现在这还有很多不足之处......

如何保存缓冲区的MSB?我首先计算移位后所需的缓冲区大小(new_length = arg_length +(shift + 7)/ 8))。并将输入复制到其中,然后不移动arg_length字节,而是移位new_length字节,解决了截断MSB的问题。

但还有另一个问题,表现。不幸的是,现代x86 CPU上的rcl速度很慢,因此以这种方式进行315位移位是非常糟糕的主意。但你没必要。你可以先将312位的移位复制到新的长度缓冲区,然后将输入数字复制39个字节(开始),然后通过上面的循环逐个移位3位。

另外,如果您将足够填充输出缓冲区,则可以使用dword / qword rcl变体(32b / 64b代码)同时处理更多字节。 (实际上从你的描述中不清楚谁负责分配输出缓冲区,如果你的代码将以某种方式在堆栈上返回(我不确定哪个ABI可以根据移位量使用动态增长的缓冲区),或者在堆上分配它,在顶部输入更多的字节,因此你可以在值的最后一个常规字节之后修改几个字节,你可以使用dword / qword,加上4 / 8B对齐(!)地址)。

编辑:word / dword rcl / rcr的引用变体只有在数组中的整数都遵循x86的little-endian方式时才能正常工作,循环遵循正确的++ / - 方向(位b0-7位于字节数组中的偏移+0,例如b80-b87位为+10偏移,右移位于MSB(+) 10)b87朝向LSB(+0)b0)。我的初始byte [edi]示例期望它以big-endian方式,MSB从偏移+0开始,LSB以+结束,因此可以按人类顺序查看位b87 .. b0,小端让它们在视觉上“反转”每个字节组(b7 .. b0 b15 .. b8 ...... ...... b87 ... b80)......至少我是这么认为的,现在我开始如此困惑。只需以一种方式编写代码,为简单的极端情况创建单元测试并验证结果+修复它以产生您期望的结果。 :d

在这种情况下,请确保您不会edisub edi,4)更新sub rdi,8,因为这会破坏CF内容,因此请利用lea edi[edi-4]方式通过寻址模式完成的简单计算。并调整计数器以具有正确的/4 || /8值。

为了获得最佳性能,可能仍然需要一次性移位1-7位:对于1位左侧,您可以保留rcl版本,对于2-7位移位,某些变体的屏蔽/ oring使用例如32b寄存器来处理缓冲区的16b读/写并将移出的位保持在上半部分中的值。或者,如果您将走得那么远,也许可以对shl/and/or的1位变体进行分析,无论它是否比rcl快。由于编译器未使用rcl,因此特定CPU可能更喜欢多个shl/and/or指令而不是单rcl

有趣的事实:我第一次单独编写的Z80汇编代码正在执行此操作,将一个大的内存区域向左(和右)移动1位。由于这个巨大的存储区域实际上是ZX Spectrum计算机的视频RAM,它实际上是左/右移动图像1个像素(ZX每像素使用1位)。

我没有意识到可以从一个旋转到另一个旋转使用CF,所以我通过单独屏蔽该位,将其复制到其他寄存器,然后将其从那里恢复到新的字节等来完成此操作。

所以我写了它,运行它(由于bug确实重置了ZX),修复了bug,运行它,并观察了图像是如何移动的......比慢10倍(大约每秒3帧)我期望从“全能的快速汇编代码”。然后我的一个朋友确实告诉我如何旋转它,这使代码运行到20 FPS(这仍然让我意识到即使是“快速组装”也不是无限制的,我必须经常编写我的代码到在ZX的屏幕上看到任何体面的东西。)

答案 1 :(得分:0)

我宁愿ROL或ROR这些值,切掉滚过的位并在下一个字节上应用它们(在对它应用相同的过程之后)