gpt4 book ai didi

gcc - 用于在 x86_64 上复制 3 个字节的 clang 与 gcc - mov 的数量

转载 作者:行者123 更新时间:2023-12-04 17:07:38 27 4
gpt4 key购买 nike

什么应该优化编译代码以将 3 个字节从一个地方复制到另一个地方,例如,使用 memcpy(,,3)看起来像,在组装说明方面?

考虑以下程序:

#include <string.h>
int main() {
int* p = (int*) 0x10;
int x = 0;
memcpy(&x, p, 4);
x = x * (x > 1 ? 2 : 3);
memcpy(p, &x, 4);
return 0;
}

这有点人为,会导致分段违规,但我需要这些说明,以便使用 -O3 进行编译不会让它全部消失。当我 compile this (GodBolt,GCC 6.3 -O3),我得到:
main:
mov edx, DWORD PTR ds:16
xor eax, eax
cmp edx, 1
setle al
add eax, 2
imul eax, edx
mov DWORD PTR ds:16, eax
xor eax, eax
ret

很棒-单 mov从内存到寄存器的 DWORD(= 4 个字节)。不错,优化了。现在让我们改变 memcpy(&x, p1, 4)进入 memcpy(&x, p1, 3) ?编译结果变为:
main:
mov DWORD PTR [rsp-4], 0
movzx eax, WORD PTR ds:16
mov WORD PTR [rsp-4], ax
movzx eax, BYTE PTR ds:18
mov BYTE PTR [rsp-2], al
mov edx, DWORD PTR [rsp-4]
xor eax, eax
cmp edx, 1
setle al
add eax, 2
imul eax, edx
mov DWORD PTR ds:16, eax
xor eax, eax
ret

我不太了解英特尔 X86_64 组件(阅读:当它很复杂时,我什至无法正确阅读),所以 - 我不太明白这一点。我的意思是,我了解前 6 条指令中发生的情况以及为什么需要这么多条指令。为什么两个 Action 还不够? A mov WORD PTR国际 al和一个 mov BYTE PTR进入 ah ?

……所以,我是来问的。在我写这个问题的时候,我注意到 GodBolt 也有 clang 作为一个选项。好吧,clang (3.9.0 -O3) 是这样做的:
main:                                   # @main
movzx eax, byte ptr [18]
shl eax, 16
movzx ecx, word ptr [16]
or ecx, eax
cmp ecx, 2
sbb eax, eax
and eax, 1
or eax, 2
imul eax, ecx
mov dword ptr [16], eax
xor eax, eax
ret

这看起来更像我的预期。什么解释了差异?

备注:
  • 如果我不初始化 x = 0,它本质上是相同的行为.
  • 其他 GCC 版本的功能与 GCC 6.3 大致相同,但 GCC 7 减少到 5 而不是 6 mov的。
  • 其他版本的 clang(从 3.4 开始)做同样的事情。
  • 如果我们为以下内容放弃 memcpy'ing,则行为是相似的:
    #include <string.h>

    typedef struct {
    unsigned char data[3];
    } uint24_t;

    int main() {
    uint24_t* p = (uint24_t*) 0x30;
    int x = 0;
    *((uint24_t*) &x) = *p;
    x = x * (x > 1 ? 2 : 3);
    *p = *((uint24_t*) &x);
    return 0;
    }
  • 如果您想与相关代码在函数中时发生的情况进行对比,请查看 thisthe uint24_t struct version (神箭)。那就看看what happens for 4-byte values .
  • 最佳答案

    您应该通过复制 4 个字节并屏蔽掉顶部的字节来获得更好的代码,例如与 x & 0x00ffffff .这让编译器知道它允许读取 4 个字节,而不仅仅是 C 源读取的 3 个字节。

    是的,这很有帮助:它使 gcc 和 clang 免于存储 4B 零,然后复制三个字节并重新加载 4。它们只是加载 4、掩码、存储和使用仍在寄存器中的值。部分原因可能是不知道 *p 是否是 *q 的别名。

    int foo(int *p, int *q) {
    //*p = 0;
    //memcpy(p, q, 3);
    *p = (*q)&0x00ffffff;
    return *p;
    }

    mov eax, DWORD PTR [rsi] # load
    and eax, 16777215 # mask
    mov DWORD PTR [rdi], eax # store
    ret # and leave it in eax as return value

    Why aren't two moves sufficient? A mov WORD PTR into al followed by a mov BYTE PTR into ah?



    AL 和AH 是8 位寄存器。您不能将 16 位字放入 AL。这就是为什么您的最后一个 clang-output 块加载两个单独的寄存器并与 shift+ or 合并的原因。 , 在它知道允许混淆 x 的所有 4 个字节的情况下.

    如果您要合并两个单独的单字节值,您可以将它们加载到 AL 和 AH,然后使用 AX,但这会导致 Intel pre-Haswell 上的部分寄存器停顿。

    您可以将字加载到 AX(或者最好是将 movzx 加载到 eax 中,原因包括正确性和避免对 EAX 旧值的错误依赖),左移 EAX,然后将字节加载到 AL。

    但是编译器并不倾向于这样做,因为部分寄存器的东西多年来一直是非常糟糕的 juju,并且只在最近的 CPU(Haswell,也许还有 IvyBridge)上有效。这会导致 Nehalem 和 Core2 出现严重停顿。 (参见 Agner Fog's microarch pdf ;搜索部分寄存器或在索引中查找它。参见 标签维基中的其他链接。)也许几年后, -mtune=haswell将启用部分寄存器技巧来保存 clang 用于合并的 OR 指令。

    而不是编写这样一个人为的函数:

    编写接受 args 并返回一个值的函数,这样您就不必让它们变得非常奇怪而不进行优化 .例如一个接受两个 int* args 并在它们之间执行 3 字节 memcpy 的函数。

    This on Godbolt (使用 gcc 和 clang),带有颜色突出显示
    void copy3(int *p, int *q) { memcpy(p, q, 3); }

    clang3.9 -O3 does exactly what you expected: a byte and a word copy.
    mov al, byte ptr [rsi + 2]
    mov byte ptr [rdi + 2], al
    movzx eax, word ptr [rsi]
    mov word ptr [rdi], ax
    ret

    为了获得您设法生成的愚蠢,首先将目标归零,然后在复制三字节后将其读回:
    int foo(int *p, int *q) {
    *p = 0;
    memcpy(p, q, 3);
    return *p;
    }

    clang3.9 -O3
    mov dword ptr [rdi], 0 # *p = 0
    mov al, byte ptr [rsi + 2]
    mov byte ptr [rdi + 2], al # byte copy
    movzx eax, word ptr [rsi]
    mov word ptr [rdi], ax # word copy
    mov eax, dword ptr [rdi] # read the whole thing, causing a store-forwarding stall
    ret

    gcc 并没有做得更好(除了在不重命名部分 regs 的 CPU 上,因为它也通过将 movzx 用于字节副本来避免对 EAX 旧值的错误依赖)。

    关于gcc - 用于在 x86_64 上复制 3 个字节的 clang 与 gcc - mov 的数量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41407257/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com