LLVM IR 函数属性

在LLVM IR中,函数属性被设置为传递关于函数的附加信息。函数属性被认为是函数的一部分,而不是函数类型的一部分,因此具有不同函数属性的函数可以具有相同的函数类型。

函数属性是遵循指定类型的简单关键字。如果需要多个属性,它们是空格分隔的。例如:

define void @f() noinline { ... }
define void @f() alwaysinline { ... }
define void @f() alwaysinline optsize { ... }
define void @f() optsize { ... }

LLVM IR 函数属性可选值如下:

  • alignstack(<n>)
    这个属性表明,当发布序言和尾声(epilogue)时,后端应该强制堆栈指针对齐。在圆括号中指定所需的对齐方式,该对齐方式必须是2的幂。

  • allocsize(<EltSizeParam>[, <NumEltsParam>])
    该属性指示注释的函数将始终返回至少给定数量的字节(或null)。它的参数是零索引参数号;
    如果提供了一个参数,则假定至少有CallSite.Args[EltSizeParam]字节在返回的指针处可用。如果提供了两个参数,则假定CallSite.Args[EltSizeParam] * CallSite.Args[NumEltsParam]字节可用。引用的参数必须是整数类型。没有关于返回的内存块的内容的假设。

  • alwaysinline:
    该属性表示内联器应尽可能将该函数内联到调用者中,而忽略此调用者的任何活动内联大小阈值。

  • builtin:
    这表明,即使函数的声明使用nobuiltin属性,调用点上的被调用函数也应该被识别为内置函数。这仅在调用点有效,才能直接调用nobuiltin属性声明的函数。

  • cold:
    这个属性表明这个函数很少被调用。在计算边权重时,由冷函数调用后支配的基本块也被认为是冷的; 因此,轻量级。

  • convergent:
    在一些并行执行模型中,存在无法根据任何附加值进行控制的操作。我们称之为这样的操作convergent,并用这个属性标记它们。
    convergent属性可能出现在函数或call/invoke指令上。当它出现在一个函数上时,它表示对这个函数的调用不应该取决于附加值的控制。例如,内在的llvm.nvvm.barrier0convergent,所以对这个内在的调用不能取决于附加值的控制。
    当它出现在一个call/invoke中时,convergent属性表明我们应该把这个调用看作是我们正在调用一个收敛函数。这对间接调用特别有用;没有这个,我们可以把这样的调用视为目标不收敛。
    当可以证明函数不执行任何收敛操作时,优化器可以删除函数的convergent属性。同样,优化程序可以在call/invoke不能调用收敛函数时删除calls/invokes上的convergent函数

  • inaccessiblememonly:
    该属性表明该函数只能访问正在编译的模块而无法访问的内存。这是一种较弱的readnone形式。

  • inaccessiblemem_or_argmemonly:
    该属性表明该函数只能访问被编译的模块而无法访问的内存,或者其指针参数指向的内存。这是一种较弱的argmemonly形式。

  • inlinehint:
    这个属性表明源代码包含一个暗示这个函数内联的提示是可取的(比如C/C++中的“inline”关键字)。这只是一个暗示; 它对内联没有要求。

  • jumptable:
    该属性表示应该在代码生成时将函数添加到跳转指令表中,并且应将所有对此函数的地址引用引用替换为对相应的跳转指令表函数指针的引用。请注意,这会为原始函数创建一个新指针,这意味着依赖于函数指针标识的代码可能会中断。所以,jumptable注解的任何函数也必须是unnamed_addr

  • minsize:
    此属性表明,优化途径(passes)和代码生成器途径之间进行选择,以使该函数的代码大小尽可能小,并执行可能牺牲运行时性能的优化,以最小化生成的代码的大小。

  • naked:
    该属性禁用该函数的序言/尾声(prologue/epilogue)发射。这可能会导致系统特定的后果。

  • no-jump-tables:
    当此属性设置为true时,可以从生成的跳转表和查找表被禁用。这些表都是会转换为小写的字母。

  • nobuiltin:
    这表明调用点的被调用方函数不被识别为内置函数。除非调用点使用该builtin属性,否则LLVM将保留原始调用并且不会使用基于内置函数的语义的等效代码替换它。这在调用点以及函数声明和定义中是有效的。

  • noduplicate:
    此属性表示对函数的调用不能重复。对noduplicate函数的调用可能会在其父函数内移动,但不能在其父函数内复制。
    包含noduplicate调用的函数可能仍然是内联候选人,前提是调用不通过内联复制。这意味着该功能具有内部链接功能,并且只有一个调用点,所以原始调用在内联后死亡。

  • noimplicitfloat: 该属性禁用隐式浮点指令。

  • noinline:
    该属性表示内联器在任何情况下都不应该内联该函数。该属性不能与alwaysinline属性一起使用。

  • nonlazybind:
    该属性禁止该函数的延迟符号绑定。如果在程序启动期间未调用该函数,则可能会以更多的程序启动时间为代价来更快地调用该函数。

  • noredzone:
    该属性指示代码生成器不应使用红色区域,即使目标特定的ABI通常允许它。

  • indirect-tls-seg-refs:此属性指示代码生成器不应使用通过段寄存器的直接TLS访问,即使特定于目标的ABI通常允许它。

  • noreturn:
    该函数属性指示函数永远不会正常返回。如果函数在动态返回时会在运行时产生未定义的行为。

  • norecurse:
    该函数属性指示该函数不会直接或间接地调用自己的任何可能的调用路径。如果该函数执行递归,这会在运行时产生未定义的行为。

  • nounwind:
    该函数属性指示该函数不会引发异常。如果该函数确实引发异常,则其运行时行为未定义。但是,标记为nounwind的函数仍可能陷入或生成异步异常。由LLVM识别以处理异步异常(如SEH)的异常处理方案仍将提供其实现定义的语义。

  • null-pointer-is-valid:如果“null-pointer-is-valid”设置为“true”,则地址空间0中的空地址被视为内存加载和存储的有效地址。
    任何分析或优化都不应将取消引用null的指针视为此函数中的未定义行为。
    注意:由于在常量表达式中查询此属性的限制,将全局变量的地址与null进行比较仍可能会计算为false。

  • optforfuzzing:该属性表示该函数应该针对最大模糊信号进行优化。

  • optnone:
    该函数属性指示大多数优化过程将跳过此函数,但过程间优化过程除外。代码生成默认为“fast”指令选择器。该属性不能与alwaysinline属性一起使用;此属性也与minsize属性和optsize属性不兼容。
    这个属性需要在noinline函数中指定属性,所以函数不会被内联到任何调用者中。只有具有该alwaysinline属性的函数才是用于内联到此函数主体中的有效候选项。

  • optsize:
    此属性表明,优化传递(passes)和代码生成器传递之间进行选择,以保持此函数的代码大小较低,否则,只要不会显着影响运行时性能,就会专门减少代码大小进行优化。

  • patchable-function:
    这个属性告诉代码生成器,为这个函数生成的代码需要遵循特定的约定,以便运行时函数稍后可以修补它。该属性本身并不意味着对程序间优化的限制。所有修补语义效应可能必须通过连接类型单独传送。该属性的确切效果取决于其字符串值,目前有一个合法的可能性:

    • prologue-short-redirect
      这种类型的可修补函数旨在支持修补函数序言,以线程安全的方式将控制权重定向到函数之外。它保证函数的第一条指令足够大以容纳短跳转指令,并且将被充分对齐以允许通过原子比较和交换指令进行完全更改。尽管可以通过插入足够大的NOP来满足第一个要求,但LLVM可以并且将尝试将现有指令(即,不得不被发射的指令)重新用作大于短跳跃的可修改指令。
      prologue-short-redirect目前仅在x86-64上受支持。
  • probe-stack:
    该属性表明该函数将在堆栈的末尾触发一个防护区域。它确保对堆栈的访问必须不会远离保护区域的大小,保护域是堆栈的先前访问。它需要一个必需的字符串值,即将被调用的堆栈探测函数的名称。
    如果具有”probe-stack“属性的函数内联到另一个”probe-stack“属性的函数中,对调用者而言,则结果函数具有”probe-stack“属性。如果具有”probe-stack“属性的函数被内联到完全没有”probe-stack“属性的函数中,则结果函数具有”probe-stack“被调用者的属性。

  • readnone:
    在一个函数上,这个属性表明函数严格基于它的参数来计算它的结果(或者决定展开一个异常),而不需要逆向引用任何指针参数或者访问任何对调用者函数可见的可变状态(例如内存,控制寄存器等)。它不写任何指针参数(包括byval参数),也不会改变调用者可见的任何状态。这意味着虽然它不能通过调用C++异常抛出方法来展开异常(因为它们会写入内存),但可能会有非C++机制在不写入LLVM可见内存的情况下抛出异常。
    在参数上,该属性指示该函数不会对指针参数进行逆向引用,即使它可以读取或写入指针指向的内存(如果通过其他指针访问的话)。
    如果readnone函数读取或写入程序可见的内存,或者有其他副作用,则行为未定义。
    如果函数读取或写入readnone指针参数,则行为未定义。

  • readonly:
    在一个函数中,这个属性表明函数不会通过任何指针参数(包括byval参数)进行写入,也不会修改调用者函数可见的任何状态(例如内存,控制寄存器等)。它可能会逆向引用(就是*号操作)指针参数并读取调用者可能设置的状态。readonly函数在调用相同的参数集和全局状态时始终返回相同的值(或者展开相同的异常)。这意味着虽然它不能通过调用C++异常抛出方法来展开异常(因为它们会写入内存),但可能会有非C++机制在不写入LLVM可见内存的情况下抛出异常。
    在一个参数上,这个属性表明函数不会通过这个指针参数写入,即使它可能写入指针指向的内存。

  • stack-probe-size“:
    该属性控制堆栈探测器的行为:”probe-stack“属性或ABI所需的堆栈探测器(如果有的话)。它定义了防护区的大小。它确保如果函数可能会使用比保护区大小更多的堆栈空间,则会发出堆栈探测序列。它需要一个必需的整数值,默认为4096
    如果具有”stack-probe-size“属性的函数内联到另一个”stack-probe-size“属性函数中,则生成的函数具有”stack-probe-size“数值较小的属性。如果具有”stack-probe-size“属性的函数被内联到完全没有”stack-probe-size”属性的函数中,则结果函数具有”stack-probe-size“被调用者的属性。

  • no-stack-arg-probe“:
    该属性禁用ABI所需的堆栈探测器(如果有的话)。

  • writeonly:
    在一个函数上,这个属性表明函数可以写入但不从内存中读取。
    在一个参数上,这个属性表明函数可以写入但不读取这个指针参数(即使它可以从指针指向的内存中读取)。
    如果writeonly函数读取程序可见的内存,或者有其他副作用,则行为未定义。
    如果函数从writeonly指针参数读取,则行为未定义。

  • argmemonly:
    这个属性表明函数内部唯一的内存访问是加载并存储指针类型参数所指向的对象的任意偏移量。换句话说,函数中的所有内存操作都可以仅使用基于其函数参数的指针来引用内存。
    请注意,argmemonly可以与readonly属性一起使用,以便指定该函数只从其参数中读取。
    如果argmemonly函数读取或写入指针参数以外的内存,或者有其他副作用,则行为未定义。

  • returns_twice:
    该属性表示该函数可以返回两次。C的setjmp这里有一个更好的例子)是这种功能的一个例子。编译器在这些函数的调用者中禁用某些优化(如tail调用)。

  • safestack:
    此属性表示已为此函数启用SafeStack保护。
    如果具有safestack属性的函数被内联到一个函数,这个函数不具有safestack属性或具有一个sspsspstrongsspreq属性,然后将所得的函数将有一个safestack属性。

  • sanitize_address:
    此属性表明已为此函数启用AddressSanitizer检查(动态地址安全分析)。

  • sanitize_memory:
    此属性表示对此函数启用MemorySanitizer检查(对未初始化内存的访问的动态检测)。

  • sanitize_thread:
    此属性表示为此函数启用了ThreadSanitizer检查(动态线程安全分析)。

  • sanitize_hwaddress:
    此属性表示为此函数启用了HWAddressSanitizer检查(基于标记指针的动态地址安全分析)。

  • speculative_load_hardening:
    此属性表示应为函数体启用 Speculative Load Hardening
    Speculative Load Hardening 是针对信息泄漏攻击的最佳努力缓解,它利用控制流错误推测
    – 特别是对是否采用分支的错误推测。
    通常,实现此类攻击的漏洞被归类为“幽灵变种#1”。
    值得注意的是,这并没有试图减少分支目标的错误推测,被归类为“幽灵变种#2”漏洞。
    内联时,该属性是粘性的。 内联带有此属性的函数将使调用者获得该属性。
    这旨在提供一个最大保守模型,其中使用此属性注释的函数中的代码将始终(即使在内联后)最终硬化。

  • speculatable:
    这个函数属性表明函数除了计算结果之外没有任何影响,并且没有未定义的行为。请注意,这speculatable还不足以断定沿着任何特定的执行路径,对此函数的调用次数不会在外部可观察到。该属性仅适用于函数和声明,而不适用于单个调用点。如果一个函数被错误地标记为speculatable,并且确实表现出未定义的行为,即使该调用点是死代码,也可能会观察到未定义的行为。

  • ssp: 该属性表示该函数应该发出一个堆栈溢出保护器(stack smashing
    detected)。它的形式是“canary” –
    在从函数返回时检查局部变量以查看它是否被覆盖之前放置在堆栈上的随机值。启发式用于确定函数是否需要堆栈保护器。使用的启发式将使保护器具有以下特性的函数:

    • 大于ssp-buffer-size(默认8)的字符数组。
    • 包含大于ssp-buffer-size的字符数组的聚合。
    • 大小大于ssp-buffer-size的变量或者常量调用alloca().

    被确定为需要保护器的变量将被安排在堆栈上,以便它们与堆栈保护器防护装置相邻。
    如果一个具有ssp属性的函数被内联到一个没有ssp属性的函数中,那么结果函数将具有一个ssp属性。

  • sspreq:
    该属性表示该函数应该始终发出堆栈溢出保护器。这覆盖了ssp函数属性。
    被确定为需要保护器的变量将被安排在堆栈上,以便它们与堆栈保护器防护装置相邻。具体的布局规则是:

    1. 大型数组和包含大型数组(>=
      ssp-buffer-size)的结构最接近堆栈保护器。
    2. 小数组和包含小数组(<
      ssp-buffer-size)的结构距离保护器第二近。
    3. 已经取得地址的变量是第三接近保护者。

    如果其具有sspreq属性的函数被内联到一个函数,它不具有sspreq属性或具有一个sspsspstrong属性,然后将所得的函数将有一个sspreq属性。

  • sspstrong:
    该属性表示该函数应该发出一个堆栈溢出保护器。该属性在确定函数是否需要堆栈保护器时会使用强启发式。强大的启发式功能可以为以下函数提供保护:

    • 任何大小和类型的阵列
    • 包含任何大小和类型的数组的聚合。
    • 调用alloca()。
    • 已经取得地址的局部变量。

    被确定为需要保护器的变量将被安排在堆栈上,以便它们与堆栈保护器防护装置相邻。具体的布局规则是:

    1. 大型数组和包含大型数组的结构(>=
      ssp-buffer-size)最接近堆栈保护器。
    2. 小数组和包含小数组(<
      ssp-buffer-size)的结构距离保护器第二近。
    3. 已经取得地址的变量是第三接近保护者。

    这覆盖了ssp函数属性。
    如果一个具有sspstrong属性的函数被内联到一个没有sspstrong属性的函数中,那么结果函数将具有一个sspstrong属性。

  • strictfp:
    该属性指示该函数是从需要严格浮点语义的作用域调用的。LLVM不会尝试任何需要假设浮点舍入模式的优化,或者可能会改变可能通过调用此函数来设置或清除的浮点状态标志的状态。

  • thunk“:
    该属性表示该函数将通过尾部调用委托给某个其他函数。不应将thunk的原型用于优化目的。预计调用者将投掷thunk原型以匹配thunk目标原型。

  • uwtable:
    这个属性表明被定位的ABI需要为这个函数生成一个展开的表入口,即使我们能够证明没有异常通过它。这通常适用于ELF
    x86-64 abi,​​但对于某些编译单元可以禁用它。

  • nocf_check:
    此属性表示不会对属性实体执行控制流检查。它会禁用特定实体的-fcf-protection = <>以细化HW控制流保护机制。该标志是目标独立的,并且当前属于函数或函数指针。

  • shadowcallstack:
    此属性表示为该函数启用了ShadowCallStack检查。仪器检查(instrumentation checks)函数的返回地址在函数prologeiplog之间没有改变。它目前是x86_64特定的