使 gcc/clang 将函数识别为内置函数

Posted

技术标签:

【中文标题】使 gcc/clang 将函数识别为内置函数【英文标题】:Make gcc/clang recognize function as builtin 【发布时间】:2014-09-04 14:40:20 【问题描述】:

在 neovim 项目中,我们使用了一些标准功能,但并未在所有目标平台上实现。值得注意的是,stpcpy,很快还有mempcpy。 Currently we're solving that by supplying and using our own x variants of these functions.

一个例子:

char *xstpcpy(char *restrict dst, const char *restrict src)
  FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL

  const size_t len = strlen(src);
  return (char *)memcpy(dst, src, len + 1) + len;

然而,这仍然不是完全最优的,因为一些编译器,比如 gcc,知道这些函数的标准版本是做什么的,并且在给定足够的上下文时可以生成更好的代码:gcc code for stpcpy builtin。

我已经考虑在它们周围放置#ifdef 守卫,只有当它们没有被定义时才应该由我们提供,并且我们应该开始使用常规名称(stpcpy 而不是xstpcpy )。但在这一点上,这将是一个更具侵入性的变化。我的问题是我是否可以告知 gcc xstpcpystpcpy 完全相同?

P.S.:一个相关问题:是否有一个标志,例如-std=c99,它强制 gcc/clang 无论如何都会发出对标准函数的调用?我似乎记得这样的事情,但现在找不到参考。如果-std=c99 确实禁用了内置扩展,我想知道如何在保持-std=c99 的同时启用内置扩展。

编辑:由于一切似乎都有些模糊,我一直在尝试一些事情。首先是代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() 
    const char str[] = "we have stpcpy";

    printf("static\n");
    
        char p1[256];
        char p2[256];

        char *end1 = stpcpy(p1, str);
        char *end2 = (stpcpy)(p2, str);

        printf("static using stpcpy?\np1 = %s (end = %p)\np2 = %s (end = %p)\n",
                p1, end1, p2, end2);
    

    return 0;

结果(我在 OSX 上,但 godbolt 表明它在 linux 上类似):

命令行:gcc-4.9 -O3 -save-temps -march=native stpcpy.c -o stpcpy gcc 4.9 似乎发出了对stpcpy_chk 的调用来代替stpcpy() 行,并发出常规_stpcpy(libc 调用)来代替(stpcpy)() 行。我本来希望 gcc 将其降低为 mempcpy,因为 stpcpy builtin code in the gcc codebase made 最初让我相信。

命令行:clang -O3 -save-temps -march=native stpcpy.c -o stpcpy (XCode clang 3.4) Clang 或多或少具有我对 gcc 的期望。它完全优化了对stpcpy 的调用。像这样创建 asm:

leaq    -258(%rbp), %rdx
movabsq $34182044572742432, %rax ## imm = 0x79706370747320
movq    %rax, -265(%rbp)
movabsq $2334402142592329079, %rcx ## imm = 0x2065766168206577
movq    %rcx, -272(%rbp)

而不是调用_stpcpy

我想知道我是否可以让 gcc-4.9 做我想做的事。使用具有不同版本的godbolt,我无法像clang那样使用gcc创建类似的代码。

【问题讨论】:

@technosaurus:我想知道-fno-builtin的反面。 @technosaurus-fno-builtin 仅适用于 GCC 4.9.x ***.com/questions/25272576/… -fno-builtin 存在的时间比 4.9 还要长... -fbuiltin(如果存在的话)是默认行为,除非指定了 -fno-builtin-ffreestanding。使用 -std=*** 更改其中任何一个都没有意义,因为该行为是实现定义的,而不是标准。尽管我不想推荐它,但 autotools 就是为这种情况而设计的。 @technosaurus,您阅读链接了吗?您是否尝试使用 -fno-builtin 与例如GCC 4.8.1 并查看程序集? @Zboson gcc.godbolt.org 使用 g++,而不是 gcc 并且 -fno-builtin 失败的任何版本都是该版本中的错误(另请注意,4.8 是第一个使用的版本C ++,因此预计会出现新错误)自动工具通常会为编译器错误添加解决方法(这就是为什么配置脚本最终可能是实际代码大小的 100 倍)...如果您想确保使用库函数(强制不使用内置函数)你可以像 (function_name)(parameters,...) 那样调用它而不是 function_name(parameters,...) ...注意函数名周围的括号。 【参考方案1】:

我不知道 stpcpy 的内置函数是如何工作的,但对于 memcpy,它要求大小是编译时间常数并且小于或等于 8192 字节。如果您的代码满足这两个要求(并且您不使用-fno-builtin),GCC 将使用内置的memcpy。我不知道有什么方法可以强制它使用 builtin 以获得更大的尺寸。

要禁用内置函数,您可以使用-fno-builtin。但是,-fno-builtinonly seems to work for GCC 4.9.x。

编辑: 要将内置函数与-std=c99 一起使用,请使用__builtin_memcpy。我刚试过这个,看了看汇编。使用memcpy 调用memcpy。使用__builtin_memcpy 直接构建内存副本。但是,如果您输入的大小大于 8192,它会调用 memcpy 函数。和-std=gnu99一样。

【讨论】:

stpcpy 的内置函数降级为mempcpy 的内置函数,因此会非常相似。我并不是要求总是强制使用内置函数,而是要求在适当的时候将我们的自定义函数替换为内置函数。 (例如:xstpcpy 应该成为适当的内置 stpcpy 如果strlen(s) &lt; 8192)。我还在某处读过内置在某些标准模式下被禁用的地方。 如果是这种情况,我想禁用该功能,但保留标准模式。 不,这将强制在所有情况下都使用内置函数。我希望它只在编译器推断它是有益的情况下使用。就像普通案例(strlenmemcpystpcpy、...),但对于“我们的”版本(xstpcpy、...)。 @Aktau,不,__builtin_memcpy 的限制和以前一样。 我对 gcc 4.8.3 上的 -fno-builtin 没有任何问题 @keltar,它不适用于 GCC 4.8.1 及更低版本。您可以通过gcc.godbolt.org 自行查看。确保大小是编译时间常数并且小于 8192。【参考方案2】:

内置函数不是用户定义的。

你可以使用例如

#ifdef HAVE_STPCPY
#define xstpcpy stpcpy
#else
char *xstpcpy(char *restrict dst, const char *restrict src);
#endif

在你的头文件中,前提是你有HAVE_* 宏定义为configure。这将允许编译器在合理的情况下使用内置函数。

至于-std=c99 - C99 没有stpcpy,它是glibc 特有的功能。您可能使用隐式声明对其进行了测试。 gcc 如果原型不同,则无法确定函数是内置替换的候选函数。这是隐式声明引入的许多问题之一。

【讨论】:

这是我想到的第一个解决方案,我认为我们可能不得不采用它,即使它有点难看。我最初想避免在配置步骤中定义“HAVE_”。顺便说一句,我们实际上是用-std=gnu99 编译的,但我们稍后也需要MSVC 兼容。但我想重要的是:即使指定了-std=c99,gcc 是否将stpcpy 作为内置函数处理? @Aktua 为什么不添加-S 编译并查看程序集以回答您的问题? 快速测试说“不,除非 -D_GNU_SOURCE 也被定义”,这是有道理的 - 如果没有这个宏,你就不能安全地使用 glibc 特定的功能。 @keltar 如果您在 stpcpy 周围添加括号,则库函数将始终被调用...#define xstpcpy (stpcpy) 参见:***.com/a/25272962/1162141 @technosaurus 这似乎主要是 gcc 的事情,clang 不尊重这一点(请参阅我的编辑)。【参考方案3】:

这可以通过定义一个与您的函数同名的宏来完成,如果您想避免在其余函数中使用内置函数,即使在使用 -fno-builtin-ffreestanding 编译时也可以工作。

例如:

#define strlen __builtin_strlen
//or
#define strlen(...) __builtin_strlen(__VA_ARGS__)

注意:如果你把它命名为my_strlen(),你可以给strlen添加一个弱别名,让strlen被一个实际的strlen()函数(如果存在)覆盖

如果该值可以在编译时计算,它将被缩减为一个常量。 如果它不能减少到一个常数,那么它会:

用自己的优化版本替换调用 回退到您的版本

strlen 确实有一个内置替换(repne scazb 变体),但我不确定是否有办法(除了将其从编译器中删除)无需代码替换即可获得常量检查通过

编辑:添加宏以检查内置函数

#ifdef __clang__
    #define HAS(...) __has_builtin(__VA_ARGS__)
#elif defined __GNUC__ //assume gcc ... (where the list came from)
    #define HAS(...) 1
#else
    #define HAS(...) 0
#endif
#if HAS(__builtin_stpcpy)
    #define stpcpy __builtin_stpcpy
#endif

【讨论】:

"注意:如果将其命名为 my_strlen(),则可以为 strlen 添加一个弱别名,以允许 strlen 被实际的 strlen() 函数(如果存在)覆盖"。我一直在考虑这个,但找不到 MSVC 是否支持这个。遗憾的是,这将是一项要求。 @Aktau 原始问题中没有提到,但显然它确实有类似的东西:***.com/a/11529277/1162141

以上是关于使 gcc/clang 将函数识别为内置函数的主要内容,如果未能解决你的问题,请参考以下文章

注入技巧:使用内置函数快速确定数据库类型

TimeValue 不是可识别的内置函数名称

STRING_AGG 不是可识别的内置函数名称

JavaScript中的内置函数

sql server 中 使用lead() over()函数报错:lead无法识别的内置函数。

内置函数和匿名函数