从clang:'byval'属性中发出llvm字节码,用于将具有重要析构函数的对象传递给函数

Cyg*_*sX1 6 llvm clang

我有一个源C++代码,我使用clang解析,生成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;}
Run Code Online (Sandbox Code Playgroud)

编译命令行:

clang++ -c cpass.cpp -emit-llvm -o cpass.bc; llvm-dis cpass.bc
Run Code Online (Sandbox Code Playgroud)

生成的输出文件(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
}
Run Code Online (Sandbox Code Playgroud)

如您所见,两个set函数的参数看起来完全相同.那么我该如何判断第一个函数是用于取值而不是指针?

一种解决方案可能是以某种方式解析受损的函数名称,但它可能并不总是可行的.如果有人提出extern "C"这个功能会怎么样?

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

Anton Korobeynikov建议我应该深入研究clang的LLVM IR发射.不幸的是,我对clang内部几乎一无所知,文档相当稀疏.clang 的内部手册不讨论IR发射.所以我真的不知道如何开始,在哪里解决问题,希望没有实际通过所有 clang源代码.有什么指针吗?提示?进一步阅读?


回应Anton Korobeynikov:

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

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

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


Ant*_*kov 5

不幸的是,"byval"不只是"注释",它的参数属性对于优化器和后端来说意味着很多.基本上,如何通过平台C++ ABI传递带有和不带有非平凡函数的小结构/类的规则,所以你不能总是在这里使用byval.

事实上,这里的byval只是前端级别的小优化的结果.当你按值传递东西时,应该在堆栈上构建临时对象(通过默认的复制ctor).如果你有一个类似POD的类,那么clang可以推断出那个副本ctor将是微不足道的并且将优化这对ctor/dtor,只传递"内容".

对于非平凡的类(如你的情况),clang不能执行这样的优化,并且必须同时调用ctor和dtor.因此,您将看到创建临时对象的指针.

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