【问题标题】:Is it possible to eliminate these redundant trailing zeros?是否有可能消除这些多余的尾随零?
【发布时间】:2017-05-29 01:17:23
【问题描述】:

我正在编写一些非常严格的 ASM 代码。

注意这组由 NASM 生成的操作码:

8AA4241C020000    mov ah,[esp+0x21c]

还有类似的:

051C020000 add eax,0x21c ; 4 extra 0's! 
8D84241C020000    lea eax,[esp+0x21c] ; Brutal! 

有什么方法可以与处理器通信,您打算将 15 位偏移量应用于 32 位寄存器,并让它自己找出 0 填充?

我一直在梳理https://c9x.me/x86/html/file_module_x86_id_176.html 以获得一些指导。这里或那里额外的 2 个字节真的会救我的命!

也接受:

重写语句以使其更小的替代方法,最终我在这种情况下要做的是:

mov eax,[esp+0x21c]
push eax 

如果有办法手动对其进行编码以使其超小,我很乐意看到这种技术。

【问题讨论】:

  • 上面有一个 LEA 示例 ;)
  • 处理器手册告诉您什么是可用的。仅 16 位寄存器支持 16 位偏移。不确定您对手动编码的含义。这不是你可以随便编造的东西。
  • 您可以将寄存器的上部归零 (xor ebx, ebx),然后将 16 位值移入 (mov bx, 21c - 注意:不使用 ebx) 并根据需要使用 (add eax, ebx )。 xor 不是免费的,但是如果您多次执行此技巧...
  • 在某些模式下,某些指令有符号扩展位。使用 16 位寄存器肯定有效,但您还必须先将它们异或以将它们“添加”到 32 位寄存器。
  • 如果你想用速度换取空间,你可以使用一些更小但更慢的指令,如loopenterleave... 或将mov eax, -1 替换为or eax, 0xFFFFFFFF。旧软件也通过自我修改代码来节省内存,或者将一些代码字节作为常量重用。更多提示Agner Fog's optimization guide for x86 platforms - 10. Optimizing for size

标签: assembly compiler-construction x86 nasm opcodes


【解决方案1】:

有没有办法与处理器沟通,您打算将 15 位偏移量应用于 32 位寄存器,并让它自己找出 0 填充?

没有。可用的指令编码记录在英特尔手册中(其在线版本可在网上的各个地方获得;请参阅 标签 wiki 中的链接)。对于MOV,偏移量大小与寄存器大小相匹配。当您 MOV 进入 16 位寄存器时,处理器仅使用 16 位偏移量。永远无法获得 15 位偏移量。

正如 Raymond Chen 所说,“这并不是说您可以随便编造 [您自己的自定义编码]”。

在某些模式下,某些指令有符号扩展位。

当然可以,但我不知道这对您有什么帮助。您的目标是减小指令的大小:添加额外的 16 位操作数大小前缀以更改对偏移大小的解释不会帮助您做到这一点。

一般来说,如果有一种更短的方法来对与原始指令等效的指令进行编码,那么汇编程序会为您发出该编码。 当然 NASM 会使用它的multi-pass optimization option(默认启用)。

这里或那里额外的 2 个字节真的会救我的命!

这不是您可以有效保存的地方之一。

正如 David Wohlferd 已经建议的那样,如果您重复执行此操作,您可以通过预先清除一个寄存器(XOR reg, reg;2 个字节)来稍微压缩代码大小,将其用作 reg 的源寄存器-reg MOVs(每个只有 2 个字节),然后对那些已经清除了高 16 位的寄存器执行 16 位 MOVs。

在处理具有大量寄存器的 ISA 时,在特定过程的上下文中使用一个来包含 0 是相对常见的做法。许多 ISA 通过使用专用的零寄存器更进一步。您也可以使用 x86 执行此操作,但考虑到 ISA 的寄存器限制程度,这通常是一种悲观。但是,如果您将优化大小放在首位,那么有时可能会有意义。 (再说一次,它可能不会,因为它可能会迫使您溢出到内存中,并且每次存储和加载都会使代码膨胀至少 2 个字节。)

实际上,我敢打赌,您的代码中还有很多其他地方会浪费指令大小,并且可以实现更显着的减少。如果您想查看代码以减小其大小,请考虑在Code Review 上发布问题(当然,假设您有工作代码)。

我不太确定在什么情况下您会编写代码来节省 2 个字节的空间。也许您正在编写一个需要容纳在 512 字节以内的引导加载程序?在这种情况下,大多数人所做的就是编写一个多阶段引导加载程序,其中第一阶段仅限于 512 字节,只需调用第二阶段,而您没有此类限制。

【讨论】:

    【解决方案2】:

    如果您有一些高 24 位归零的寄存器,那么例如(对于 eax 归零)可以减少 2 个字节:

    ; additional 2 bytes ruining the saving, if you don't have zero reg.
    ; b0 87                   mov    al,0x87
    
    ; 5 byte fetch of value
    b0 87                   mov    al,0x87
    8a 24 84                mov    ah,BYTE PTR [esp+eax*4] 
    

    或者如果你知道你在其他一些寄存器中有一些低值 104..540(只有其中一些是合适的),你可以稍微降低它的偏移量,例如假设你知道 ebx == 104:

    8a 64 9c 7c             mov    ah,BYTE PTR [esp+ebx*4+(0x21C-104*4)]
    

    如果这将是真正的大小挑战,您必须发布整个代码,因为可能有(而且经常是)疯狂的方法来以非常意想不到和几乎无法想象的方式来节省大小。

    【讨论】:

      猜你喜欢
      • 2011-05-30
      • 2012-01-26
      • 2021-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多