当前位置: 首页 > 知识库问答 >
问题:

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

柯轶
2023-03-14
struct Vec3{
    double x, y, z;
};

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

返回时,%RAX将包含调用方在%RDI中传入的地址。

考虑到这一点,下面的(愚蠢的)函数:

struct Vec3 create(void);

struct Vec3 use(){
    return create();
}
use_v2:
        jmp     create
use:
        pushq   %r12
        movq    %rdi, %r12
        call    create
        movq    %r12, %rax
        popq    %r12
        ret

不用说,对于SSE类的类型(例如,只有2个和不是3个doubles),会执行尾调优化(至少由gcc和clang,在godbolt上运行):

struct Vec2{
    double x, y;
};

struct Vec2 create(void);

struct Vec2 use(){
    return create();
}

中的结果

use:
        jmp     create

共有1个答案

斜单鹗
2023-03-14

看起来像是一个漏掉的优化bug,如果还没有为gcc和CLANG打开重复的bug的话,您应该报告这个bug。

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

我们可以看到GCC正在返回自己的传入arg,而不是返回create()将在RAX中返回的arg副本。这是错过的优化,阻止了尾调优化。

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

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

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

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

然而,这种情况接近于它会有用的情况。如果我们将自己的retval空间传递给子函数,我们可能需要在调用后修改该空间。然后,可以方便地访问该空间,例如,在返回之前修改一个返回值。

#define T struct Vec3

T use2(){
    T tmp = create();
    tmp.y = 0.0;
    return tmp;
}

高效手写ASM:

use2:
        callq   create
        movq    $0, 8(%rax)
        retq
# clang -O3
use2:                                   # @use2
        pushq   %rbx
        movq    %rdi, %rbx
        callq   create
        movq    $0, 8(%rbx)
        movq    %rbx, %rax
        popq    %rbx
        retq
 类似资料:
  • 我有一个实现Iterable的类,以便用户可以使用迭代器。我使用泛型来允许用户使用任何类型并使用该类。 这是下面的工作代码,没有警告- 但是,如果我将ListIterator定义如下- 我在 Eclipse 中收到警告, 当我在类后指定泛型类型时,它为什么会抱怨?为了能够在我的类中使用Type,我不应该这样做吗?我在定义CustomStackUsingArray时添加了类型,效果很好。

  • 问题内容: 在Java 7和更高版本中,菱形通常可以像这样毫无问题地用于推断类型: 但是,它不能用于这样的匿名内部类: 为什么是这样?从逻辑上讲,在这种情况下,我绝对可以将类型推断为。做出该决定的逻辑上的理由是,实际上不能在匿名内部类上推断类型,还是出于其他原因而将其省略了? 问题答案: 在JSR-334中: 不支持将Diamond与匿名内部类一起使用,因为这样做通常需要扩展类文件签名属性以表示不

  • 据我所知,init block是一个在任何构造函数之前执行的块,每当该构造函数用于创建对象时。但是为什么规则在这里矛盾...... 这里,由于只形成了子类对象,那么为什么要调用父类的init块呢?

  • 问题内容: 在Python中,以下代码会产生错误: (错误是“ TypeError:无法连接’str’和’int’对象”)。 当Python解释器遇到这些类型的串联时,为什么为什么不自动尝试使用str()函数? 问题答案: 问题在于转换是模棱两可的,因为它意味着 字符串连接 和 数字加法 。以下问题同样有效: 为什么Python解释器在遇到这些类型的 添加* 时不自动尝试使用 int() 函数?

  • 问题内容: 在以下代码中- 如果在eclipse的调试窗口中看到,则两个对象(和)都包含变量(和)的两个值。 我确实知道在多态中,子类也可以使用超类的泛型方法。但是,即使在隐藏的情况下,为什么子类对象也存储超类变量的值。有什么用吗? 问题答案: 首先 :作为一般规则,如果类定义了子类可以访问的字段,则子类 不应 重新定义该字段。这真是个坏主意。首先,您所看到的是使 私有 字段正常工作。重新定义子类

  • 假设我正在为Akka(类型化)执行元定义一个行为,该行为将执行并发计算,将其结果报告给生成它的执行元,然后停止。 如果我用计算的所有输入初始化这个actor,加上对它的“父级”的引用,那么它将永远不需要接收任何类型的传入消息。 我将使用创建此行为,向它传递一个执行计算的函数,然后返回。 Akka(Typed)要求我为将返回此行为的函数提供某种的结果类型。虽然我可以为分配行为将发送的结果消息的类型,