代码之家  ›  专栏  ›  技术社区  ›  mafu

GCC是如何进行这种优化的,为什么clang没有?

  •  0
  • mafu  · 技术社区  · 4 年前

    这是有问题的代码:

    struct Cell
    {
        Cell* U;
        Cell* D;
        void Detach();
    };
    
    void Cell::Detach()
    {
        U->D = D;
        D->U = U;
    }
    

    clang-14-O3产生:

    mov     rax, qword ptr [rdi]         <-- rax = U
    mov     rcx, qword ptr [rdi + 8]     <-- rcx = D
    mov     qword ptr [rax + 8], rcx     <-- U->D = D
    mov     rcx, qword ptr [rdi + 8]     <-- this queries the D field again
    mov     qword ptr [rcx], rax         <-- D->U = U
    

    gcc 11.2-O3产生的几乎相同,但遗漏了一个 mov :

    mov     rdx, QWORD PTR [rdi]
    mov     rax, QWORD PTR [rdi+8]
    mov     QWORD PTR [rdx+8], rax
    mov     QWORD PTR [rax], rdx
    

    Clang读取D字段两次,而GCC只读取一次并重新使用它。显然,GCC并不害怕第一个赋值会改变任何对第二个赋值有影响的东西。我想知道这是否/何时被允许。

    当U或D指向它们自己、彼此和/或同一目标时,检查正确性会变得有点复杂。

    我的理解是,GCC的较短代码是正确的,如果它保证指针指向一个单元格的开头(从不在它内部),无论它是哪个单元格。

    根据这一思路,当a)单元格始终与其大小对齐,并且b)没有对这样的指针进行自定义操作(引用和算术都很好)时,就会出现这种情况。 我怀疑情况a)是由编译器保证的,情况b)需要调用某种未定义的行为,因此可以忽略。 这可以解释为什么GCC允许自己进行这种优化。

    我的推理正确吗?如果是这样,clang为什么不进行同样的优化?

    0 回复  |  直到 4 年前
        1
  •  0
  •   supercat    4 年前

    C和C++中有许多潜在的优化,它们通常是安全的,但并不完全合理。如果有人认为 -> 运算符可用于构建标准布局对象,而无需首先在其上使用placement new(许多代码都依赖的抽象模型,无论标准是否强制支持),删除 if (mode) 在下面的C和C++函数中将是这样的优化。

    C版本:

    struct s { int x,y; }; /* Assume int is 4 bytes, and struct is 8 */
    
    void test(struct s *p1, struct s *p2, int mode)
    {
        p1->y = 1;
        p2->x = 2;
        if (mode)
            p1->y = 1;            
    }
    

    C++版本:

    #include <new>
    struct s { int x,y; };
    void test(void *vp1, void *vp2, int mode)
    {
        if (1)
        {
            struct s* p1 = new (vp1) struct s;
            p1->x = 1;            
        }
        if (1)
        {
            struct s* p2 = new (vp2) struct s;
            p2->y = 2;
        }
        if (mode)
        {
            struct s* p3 = new (vp1) struct s;
            p3->x = 1;            
        }
    }
    

    除非p2中的地址比p1高四个字节,否则优化将是正确的。在C或C++中使用的“传统”抽象模型下,如果 p1 碰巧 0x1000 以及 p2 碰巧 0x1004 ,第一个赋值将导致地址0x1000-0x1007保持 struct s ,如果还没有,则其第二个成员(位于地址0x1004)将等于1。第二个赋值通过覆盖该对象,将结束其生存期,并导致地址0x1004到0x100B包含 结构 其第一个成员将等于2。第三个任务, 如果执行 ,将结束第二个对象的生命周期并重新创建第一个对象。

    如果执行第三个赋值,则在地址0x1000处会有一个对象,其第二个字段(在地址0x1004处)将保持可读值1。如果跳过赋值,则地址0x1004处会有一个对象,其第一个字段将包含值2。在这两种情况下都会定义行为,不知道应用哪种情况的编译器必须通过使0x1004处的值取决于 mode

    碰巧的是,clang的作者似乎没有提供这种情况,因此省略了条件检查。虽然我认为标准应该使用一个允许这种优化的抽象模型,同时在不涉及奇怪的混叠角情况的情况下支持公共结构创建模式,但我看不出有任何方法可以在不允许编译器任意破坏大量现有代码的情况下解释标准,从而允许这种优化。

    我不认为有任何通用的方法可以知道,当gcc或clang决定不实施特定的优化时,代表着对优化不正确的潜在角落情况的认识,并且无法证明这些情况都不适用,当它只是代表一种疏忽,可以“纠正”,以不健全的优化取代正确的行为。