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

为什么不对类内存类型执行tailcall优化?

  •  2
  • ead  · 技术社区  · 7 年前

    System V AMD64 - ABI

    对于以下数据类型

    struct Vec3{
        double x, y, z;
    };
    

    类型 Vec3 属于类内存,因此ABI指定了关于“返回值”的以下内容:

    1. 如果类型具有类内存,则调用方为返回值提供空间,并将此存储的地址传递到%rdi中,就像 它是函数的第一个参数。实际上,这个地址 成为隐藏的第一个参数。此存储不能与任何 被调用者通过此参数以外的其他名称可见的数据。

      打开 返回%rax将包含 %rdi中的呼叫者。

    struct Vec3 create(void);
    
    struct Vec3 use(){
        return create();
    }
    

    可以编译为:

    use_v2:
            jmp     create
    

    在我看来,尾随优化是可以执行的,正如我们得到ABI的保证,即 create %rdi 将值传入 %rax

    on godbolt ). 生成的程序集代码将保存 %rdi公司 只能将其值移动到 %雷克斯 ,例如gcc:

    use:
            pushq   %r12
            movq    %rdi, %r12
            call    create
            movq    %r12, %rax
            popq    %r12
            ret
    


    on godbolt

    struct Vec2{
        double x, y;
    };
    
    struct Vec2 create(void);
    
    struct Vec2 use(){
        return create();
    }
    

    结果

    use:
            jmp     create
    
    0 回复  |  直到 7 年前
        1
  •  7
  •   Peter Cordes    7 年前

    如果gcc和clang还没有一个重复的open for gcc和clang,那么您应该报告一个遗漏的优化bug。

    (在这种情况下,gcc和clang都有相同的漏掉优化的情况并不少见; 假设某个东西是非法的仅仅因为编译器没有这样做。 执行优化:这可能是编译器的错误,或者至少有些编译器开发人员根据他们对任何标准的解释,认为它是安全的。)


    我们可以看到GCC返回它自己的传入参数,而不是返回 create() 会在皇家空军回来。 是阻碍tailcall优化的遗漏优化。

    ABI需要一个具有内存类型返回值的函数来返回RAX中的“隐藏”指针 1 .

    GCC/clang已经意识到,通过传递自己的返回值空间,而不是分配新的空间,可以省去实际的复制。但是,要进行tailcall优化,他们必须意识到可以将被调用者的RAX值保留在RAX中,而不是将传入的RDI保存在呼叫保留寄存器中。

    如果ABI不需要返回RAX中的隐藏指针,我希望gcc/clang在作为优化的tailcall的一部分传递传入的RDI时不会有任何问题。

    通常编译器喜欢缩短依赖链;这可能就是这里的原因。编译器不知道 rdi arg到 rax 可能只有一个 mov r12


    8(%rsp) 而不是真正使用RAX。至少在编译器生成的代码中,RAX返回值通常不会被使用。(如有必要,呼叫方可随时将其保存在某个地方。)

    如中所述 What prevents the usage of a function argument as hidden pointer?

    如果调用者希望将地址存储在某个地方(如果它是静态地址或堆栈地址),那么将指针放在寄存器中只会在调用者中保存一个LEA。

    然而,这个案子很接近 要有用。 修改 通话后的那个空间。然后它有助于轻松访问该空间,例如在返回之前修改返回值。

    #define T struct Vec3
    
    T use2(){
        T tmp = create();
        tmp.y = 0.0;
        return tmp;
    }
    

    use2:
            callq   create
            movq    $0, 8(%rax)
            retq
    

    与GCC9.1复制相比,实际的clangasm至少仍然使用返回值优化。( Godbolt

    # clang -O3
    use2:                                   # @use2
            pushq   %rbx
            movq    %rdi, %rbx
            callq   create
            movq    $0, 8(%rbx)
            movq    %rbx, %rax
            popq    %rbx
            retq
    

    必须保存一个指针,如果ABI没有在RAX中提供它)。我没试过那个案子。

        2
  •  -1
  •   cmwt    7 年前

    System V AMD64 - ABI 将从寄存器中的函数返回数据 RDX RAX XMM0 XMM1 Godbolt 优化似乎是基于大小。编译器最多只返回2 double float 在寄存器中。


    编译器总是错过优化。与Scheme不同,C语言没有尾部调用优化。GCC和Clang已经表示,他们没有计划尝试和保证尾部调用优化。这听起来像是OP可以尝试询问编译器开发人员,或者用这些编译器打开一个bug。