优化处理标签(空结构)函数参数

问题描述 投票:4回答:1

在某些情况下,我们使用标签来区分功能。标记通常是一个空结构:

struct Tag { };

假设我有一个使用此标记的函数:

void func(Tag, int a);

现在,让我们调用这个函数:

func(Tag(), 42);

并查看生成的x86-64反汇编,godbolt

mov     edi, 42
jmp     func(Tag, int)            # TAILCALL

没关系,标签得到了完全优化:没有为它分配寄存器/堆栈空间。

但是,如果我查看其他平台,标签有一些存在。

在ARM上,r0用作标记,它变为零(似乎不必要):

mov     r1, #42
mov     r0, #0
b       func(Tag, int)

使用MSVC,ecx用作标记,并从堆栈“初始化”(再次,似乎不必要):

movzx   ecx, BYTE PTR $T1[rsp]
mov     edx, 42                             ; 0000002aH
jmp     void func(Tag,int)                 ; func

我的问题是:是否有标记技术,在所有这些平台上同样优化?


注意:我没有找到SysV ABI指定在参数传递时可以优化空类的位置...(甚至,Itanium C++ ABI说:“空类将与普通类没有区别地传递”。)

c++ arm x86-64 calling-convention abi
1个回答
2
投票

我认为这里的基本问题是,在生成函数的独立版本时,编译器必须生成可由任何人根据相应的调用约定从任何地方调用的代码。当在不知道其定义的情况下生成对函数的调用时,所有编译器确实知道该函数期望根据调用约定来调用。基于此,似乎除非调用约定指定删除空类型的函数参数,否则编译器通常不能真正优化函数调用中的参数。现在,除非函数具有非C ++语言链接(例如,extern "C"函数),否则C ++编译器在技术上可以合理地构成它认为适合于现场给定函数签名的任何调用约定。但在实践中,这很可能不是那么简单。首先,您需要一种算法,该算法可以决定给定函数签名的最佳调用约定。第二,链接代码的能力可能与实际相关,这些代码不一定都是使用完全相同的编译器使用完全相同的编译器生成完全相同的标记,而C ++标准并不需要。函数调用约定优化肯定不是不可能的。但是我不知道任何C ++编译器实际上是这样做的(生成目标代码时)。

一种可能的解决方案是,例如,为实际的函数实现使用不同的名称,并具有简单的内联包装函数,将带有Tag类型的调用转换为相应的实现:

struct TagA { };
struct TagB { };

inline void func(int a, TagA)
{
    void funcA(int a);
    funcA(a);
}

inline void func(int a, TagB)
{
    void funcB(int a);
    funcB(a);
}

void call() {
    func(42, TagA());
    func(42, TagB());
}

try it out here

另外,请注意,虽然编译器可能会生成初始目标文件中的函数调用,但链接时优化可能最终可以去掉未使用的参数。至少有一个主要的编译器甚至documents这样的行为......

© www.soinside.com 2019 - 2024. All rights reserved.