7

我一直在浏览一些汇编编程视频,以更好地了解如何手动优化编译后留下的 *.s 文件gcc/g++ -S ... 涵盖的主题之一是重构冗余代码,它演示了如何将冗余代码移动到自己的标记块以ret结尾并用call替换它。

视频中给出的示例是 2 个块,其中包含:

mov eax,power
mul ebx
mov power,eax
inc count

它替换为call CalculateNextPower和 CalculateNextPower 看起来像:

CalculateNextPower:
mov eax,power
mul ebx
mov power,eax
inc count
ret

出于好奇,为了减小编译大小,我使用 -S 和各种优化(包括 -Os、-O2、-O3、-pipe、-combine 和 -fwhole-program)编译了一些 C 和 C++ 项目,并分析了生成的 *.s使用轻微修补(用于 .s 文件)版本的duplo来实现冗余文件。只有 -fwhole-program (现在已弃用 IIRC)对消除文件之间的重复代码有显着影响(我假设它的替换 -flto 在链接时会表现相似 - 大致相当于使用 -ffunction-sections -fdata-sections 进行编译并与 --gc-sections 链接),但仍然遗漏了大量的代码块。

当仅对具有至少 5 个连续重复指令的连续程序块进行重复数据删除时,使用 duplo 输出进行手动优化可在随机 C 项目中减少约 10% 的大小,在随机 C++ 项目中减少近 30%。

我是否缺少一个编译器选项(甚至是一个独立工具),该选项在编译大小时会自动消除冗余程序集(包括其他编译器:clang、icc 等) ,还是缺少此功能(出于某种原因?)?

如果它不存在,则可以修改duplo以忽略以“。”开头的行。或者 ';' (和其他人?)并用重复代码的函数调用替换重复的代码块,但我愿意接受其他直接与编译器的内部表示(最好是clang或gcc)一起工作的建议。

编辑:我修补了 duplo 以在此处识别重复程序集的块,但目前仍需要手动重构。只要使用相同的编译器生成代码,就有可能(但可能很慢)识别最大的重复代码块,将它们放在自己的“功能”块中,并用对该块的 CALL 替换代码.

4

2 回答 2

2

你想要的是一个克隆检测器工具

这些存在于各种实现中,具体取决于正在处理的文档元素的粒度以及可用结构的多少。

那些匹配原始行的(对你不起作用,你想通过不同的常量[数据和索引偏移]和/或命名位置或其他命名子程序来参数化你的子程序)。基于令牌的检测器可能会起作用,因为它们将识别变化的单点位置(例如,常量或标识符)。但是你真正想要的是一个结构匹配器,它可以挑选出变体寻址模式,甚至是块中间代码中的变体(参见我碰巧构建的基于 AST 的克隆检测器)。

要使用结构进行检测,您必须具有结构。幸运的是,即使是汇编语言代码也具有语法形式的结构,以及由子例程入口和出口分隔的代码块(后者在汇编中检测起来有点问题,因为每个代码块可能不止一个)。

当您检测使用结构时,您至少有可能使用结构来修改代码。但是,如果您将程序源表示为一棵树,则您具有可以检测克隆的结构(子树和子树序列),并且可以通过修改匹配点处的树来抽象克隆匹配。(我的 COBOL 克隆检测器的早期版本将克隆抽象到 COPY 库中。我们停止这样做主要是因为您不想以这种方式抽象每个克隆)。

于 2014-01-21T03:24:18.403 回答
0

您提出的建议称为程序抽象,并且已由多个小组作为研究项目实施。这是一个。 这是另一个。 还有一个。

克隆检测通常在源代码的上下文中使用,尽管它的功能是相似的。由于过程抽象发生在较低级别,它可以完成更多。例如,假设有两个对不同函数的调用,但具有完全相同的复杂参数计算。过程抽象器可以将参数计算拉入过程,但克隆检测器很难做到这一点。

我不相信 gcc 或 llvm 目前支持 PA 的实现。我搜索了两组文件,但没有找到。在上述至少两种情况下,优化器在 gcc 生成的汇编代码上运行,而不是作为 gcc 内部优化。这可能解释了为什么这些技术没有内置到编译器中。您可以尝试作者来查看他们的实现在哪里。

于 2014-01-24T04:38:30.137 回答