gpt4 book ai didi

c - 为什么 clang 和 gcc 会产生次优输出(复制结构)以将指针传递给按值结构 arg?

转载 作者:行者123 更新时间:2023-12-05 04:27:19 32 4
gpt4 key购买 nike

我有一个 C 程序,例如:

#include <stdio.h>

struct sa {
char buffer[24];
};

void proceed(const struct sa *data);

static inline void func(struct sa sa) {
proceed(&sa);
}

void test(struct sa sa) {
func(sa);
}

看来在test函数的最优汇编输出中,它的sa参数的地址可以直接传给proceed函数,因为 proceed 函数保证不会更改 data。但是,编译器(x86-64 clang 14.0 和 gcc 12.1,-O3 优化级别)发出的程序集如下:

test:                                   # @test
sub rsp, 24
mov rax, qword ptr [rsp + 48]
mov qword ptr [rsp + 16], rax
movaps xmm0, xmmword ptr [rsp + 32]
movaps xmmword ptr [rsp], xmm0
mov rdi, rsp
call proceed
add rsp, 24
ret

请注意,在输出中,整个 sa 结构从 [rsp + 32] 复制到 [rsp]。为什么编译器不消除这样的拷贝?

最佳答案

这显然是一个遗漏的优化错误,因为它只发生在额外级别的内联 func() 中。

您可以在 https://github.com/llvm/llvm-project/issues 上报告错误和 https://gcc.gnu.org/bugzilla/enter_bug.cgi?product=gcc (GCC 错误报告更喜欢 AT&T 语法,所以在您的 Godbolt 链接中选择它;在 GCC 错误报告中包含一个 Godbolt 链接以及实际代码和 asm 通常是好的,这样 future 的读者可以很快检查它是否已被修复,或玩弄它。)

对于 GCC,使用关键字 missed-optimization


since the proceed function is guaranteed not to change data

不,丢弃 const 是合法的,因为原始指向的对象不是 const。但它仍然不需要复制;您的函数拥有其堆栈参数空间,并且可以让其他函数根据需要修改唯一的副本。 (大多数调用约定都是这样工作的,包括所有 System V 约定,例如此处使用的 x86-64 SysV。另外我认为 Windows x64,其中大于 8 字节的参数通过非常量(?)传递给保留空间由调用者使用寄存器中的指针,或者如果前面有 4 个或更多参数则在堆栈中。)

test 的调用者不能假定它是未修改的,因此使用相同 arg 的另一个调用将需要在返回后重新复制结构。甚至 foo(const struct sa); 也会这样工作;无法声明/ promise 函数不会将其堆栈参数空间重用于临时空间或尾调用的参数。


This test-case on Godbolt 表明这是一个遗漏的优化:如果 noinlinetest 将仅使用 jmp func 进行尾调用,而不是在那里复制任何参数。 func 的非内联定义也不会复制,只是预期的 RSP 对齐然后 lea rdi, [rsp+16]/call proceed 将指针传递给它的堆栈参数。

因此将 __attribute__((noinline)) 添加到您的 func 将导致您的 test 调用 proceed 而不复制 arg,在执行路径中只有一个额外的 jmp。如果那是合法的,那么在内联 func 时这样做也是合法的。

struct sa {
char buffer[24];
};
void proceed(const struct sa *data);

__attribute__((noinline))
static void func(struct sa sa) {
proceed(&sa);
}

void test_struct(struct sa sa) {
func(sa);
}
// same as non-inline func()
// void test_struct_direct(struct sa sa) { proceed(&sa); }
# clang (trunk) -O3
# GCC is equivalent but uses sub/add instead of dummy push/pop
test_struct:
jmp func # TAILCALL
func:
pushq %rax # re-align the stack by 16
leaq 16(%rsp), %rdi
callq proceed
popq %rax # clean up the stack
retq

请随意在您的错误报告中短链接确切的 Godbolt 链接,或者添加评论或未评论的内容;它使用每晚构建的 GCC 和 clang,因此开发人员会知道它尚未修复。也可以随意链接此 Stack Overflow Q&A,但您的错误报告应该是独立的,并指出优化是合法的,并且取消注释 __attribute__((noinline)) 会有所不同。

(因此,对于错误报告,您可能希望将 noinline 注释掉,并取消注释手动内联 functest_struct_direct 版本。 )

关于c - 为什么 clang 和 gcc 会产生次优输出(复制结构)以将指针传递给按值结构 arg?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72859532/

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