gpt4 book ai didi

llvm - 从 clang : 'byval' attribute for passing objects with nontrivial destructor into a function 发出 llvm 字节码

转载 作者:行者123 更新时间:2023-12-04 15:15:04 25 4
gpt4 key购买 nike

我有一个使用 clang 解析的 C++ 源代码,生成 llvm 字节码。从这一点上我想自己处理文件......
但是我遇到了一个问题。考虑以下场景:
- 我创建了一个具有非平凡析构函数或复制构造函数的类。
- 我定义了一个函数,该类的对象作为参数按值(无引用或指针)传递。

在生成的字节码中,我得到了一个指针。对于没有析构函数的类,参数被注释为“byval”,但在这种情况下并非如此。
结果,我无法区分参数是按值传递,还是真的是按指针传递。

考虑以下示例:

输入文件 - cpass.cpp:

class C {
public:
int x;
~C() {}
};

void set(C val, int x) {val.x=x;};

void set(C *ptr, int x) {ptr->x=x;}

编译命令行:
clang++ -c cpass.cpp -emit-llvm -o cpass.bc; llvm-dis cpass.bc

生成的输出文件(cpass.ll):
; ModuleID = 'cpass.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"

%class.C = type { i32 }

define void @_Z3set1Ci(%class.C* %val, i32 %x) nounwind {
%1 = alloca i32, align 4
store i32 %x, i32* %1, align 4
%2 = load i32* %1, align 4
%3 = getelementptr inbounds %class.C* %val, i32 0, i32 0
store i32 %2, i32* %3, align 4
ret void
}

define void @_Z3setP1Ci(%class.C* %ptr, i32 %x) nounwind {
%1 = alloca %class.C*, align 8
%2 = alloca i32, align 4
store %class.C* %ptr, %class.C** %1, align 8
store i32 %x, i32* %2, align 4
%3 = load i32* %2, align 4
%4 = load %class.C** %1, align 8
%5 = getelementptr inbounds %class.C* %4, i32 0, i32 0
store i32 %3, i32* %5, align 4
ret void
}

可以看到, set的参数功能看起来完全一样。那么我怎么知道第一个函数是要按值而不是指针来获取参数呢?

一种解决方案可能是以某种方式解析损坏的函数名称,但它可能并不总是可行的。如果有人把 extern "C"怎么办在函数之前?

有没有办法告诉 clang保留 byval注释,还是为通过值传递的每个函数参数生成一个额外的注释?

Anton Korobeynikov 建议我应该深入研究 clang 的 LLVM IR 发射。不幸的是,我对 clang 内部结构几乎一无所知,文档相当稀少。 Internals Manual的铛不谈论红外发射。所以我真的不知道如何开始,从哪里开始解决问题,希望不需要真正浏览所有的 clang 源代码。任何指针?提示?进一步阅读?

回应 Anton Korobeynikov:

我或多或少知道 C++ ABI 在参数传递方面的样子。在这里找到了一些不错的读物: http://agner.org./optimize/calling_conventions.pdf .但这非常依赖平台!这种方法在不同的体系结构或某些特殊情况下可能不可行。

例如,在我的情况下,该函数将在与调用它的位置不同的设备上运行。这两个设备不共享内存,因此它们甚至不共享堆栈。除非用户传递一个指针(在这种情况下我们假设他知道他在做什么),一个对象应该总是在函数参数消息中传递。如果它有一个非平凡的复制构造函数,它应该由调用者执行,但对象也应该在参数区域中创建。

所以,我想做的是以某种方式覆盖 clang 中的 ABI,而不会过多地侵入他们的源代码。或者添加一些额外的注释,在正常的编译管道中会被忽略,但我可以在解析 .bc/.ll 文件时检测到。或者以某种方式重建函数签名。

最佳答案

不幸的是,“byval”不仅仅是“注释”,它还是参数属性,这对优化器和后端来说意义重大。基本上,如何传递带有和不带有非平凡函数的小结构/类的规则是由平台 C++ ABI 管理的,所以你不能总是在这里使用 byval。

其实这里的byval只是前端层面小幅优化的结果。当您按值传递内容时,应在堆栈上构造临时对象(通过默认复制构造函数)。当您有一个类似于 POD 的类时,clang 可以推断出复制 ctor 将是微不足道的,并将优化 ctor/dtor 对,只传递“内容”。

对于非平凡的类(如您的情况),clang 无法执行此类优化,必须同时调用 ctor 和 dtor。因此,您会看到创建了指向临时对象的指针。

尝试调用您的 set() 函数,您会看到那里发生了什么。

关于llvm - 从 clang : 'byval' attribute for passing objects with nontrivial destructor into a function 发出 llvm 字节码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6549623/

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