LLVM IR Comdats

Comdat IR提供对COFF和ELF目标文件COMDAT功能的访问。

COMDAT,即common data. 编译器将一些函数(具体是哪些函数,编译器自行决定)打包放到单独的section中,这有个专有名词叫COMDAT,即common data,意思是打包的函数或者打包的数据。按微软大拿Raymond Chen的说法,COMDAT这个概念最早来自FORTRAN语言。gcc和llvm对COMDAT都有对应的支持。链接器在链接阶段,可以对COMDAT中重复的函数进行消重(folding,折叠)。如果编译器不把函数打包成COMDAT项,链接器是不敢贸然优化掉对应的函数的,因为缺少这些函数的引用信息。

Comdats有一个代表COMDAT键的名称。如果链接器选择了某个其他键的键,则指定的这个键的所有全局对象只会在最终的对象文件中结束。如果有别名,别名将放置在相同的COMDAT中以及进行别名计算。

Comdats有一种选择类型来提供关于链接器如何在两个不同对象文件中的键之间进行选择的输入。

Syntax:

$<Name> = comdat SelectionKind

选择种类必须是以下之一:

类型 说明
any 链接器可以选择任何COMDAT键,选择是任意的。
exactmatch 链接器可以选择任何COMDAT键,但这些section必须包含相同的数据。
largest 链接器将选择包含最大值COMDAT键的section。
noduplicates 链接器要求只有具有此COMDAT密钥的section存在。
samesize 链接器可以选择任何COMDAT键,但这些section必须包含相同数量的数据。

请注意,Mach-O平台不支持COMDAT key,而ELF和WebAssembly仅支持any作为选择类型。

这里是COMDAT组的一个例子,其中只有当COMDAT键的section最大时才会选择一个函数:

$foo = comdat largest
@foo = global i32 2, comdat($foo)

define void @bar() comdat($foo) {
ret void
}

作为一个语法糖,$name如果名称与全局名称相同,则可以省略:

$foo = comdat any
@foo = global i32 2, comdat

在COFF对象文件中,这将创建一个COMDAT section,它的选择类型是IMAGE_COMDAT_SELECT_LARGEST,包含@foo符号的内容和另一个COMDAT section;这个section的选择类型是IMAGE_COMDAT_SELECT_ASSOCIATIVE,这个选择类型与第一个COMDAT section相关并包含在@bar符号的内容。

全局对象的属性有一些限制。它或它的别名在定位COFF时必须与COMDAT组具有相同的名称。COFF对象的内容和大小可以在链接期间使用,根据选择种类确定选择哪个COMDAT组。因为对象的名称必须与COMDAT组的名称相匹配,所以全局对象的链接不能是本地的;如果符号表中发生冲突,则可以重命名本地符号。

组合使用COMDATS和段(section)属性可能会产生令人惊讶的结果。例如:

$foo = comdat any
$bar = comdat any
@g1 = global i32 42, section "sec", comdat($foo)
@g2 = global i32 42, section "sec", comdat($bar)

从对象文件的角度来看,这需要创建具有相同名称的两个段(section)。这是必要的,因为全局变量属于不同的COMDAT组,在对象文件级别,COMDAT由段(section)表示。

请注意,除了使用COMDAT IR指定的内容之外,某些IR结构(如全局变量和函数)可能会在对象文件中创建COMDAT。当代码生成器配置为在各个段(section)中发出全局变量时(例如,向llc提供-data-sections-function-sections选项时),就会出现这种情况。