使用内联函数保持与程序集的兼容性

时间:2016-03-29 11:48:19

标签: c gcc assembly inline gnu-toolchain

我正在编写一些头文件,这些文件将由C代码和程序集访问。为此,使用C预处理器对汇编代码进行预处理。

问题是我在这些头文件中有很多inline个函数。汇编程序无法处理函数,这些函数不是目标文件中的符号(与static inline函数一样),因此我无法使用它们。我已经阅读了thisthis个宝贵的帖子,并且已经掌握了如何将externstaticinline结合使用,但我不确定如何使inline功能可以访问 C代码和程序集。

我目前的方法是编写inline函数(使用> = GNU99,-O3内联函数,其他任何调用该函数的外部定义,我需要明确定义)头文件并在实现文件中写入外部定义。 C代码包括头文件(inline函数)与-O3编译,因此使用内联版本。汇编代码使用外部定义。

问题:

  1. 汇编代码只能调用函数,内联目前是不可能的。无论如何,汇编代码可以使用内联吗?我的意思是在.S文件中,而不是内联汇编。

  2. extern inline与我当前的方法一样好,但它归结为一个定义(外部定义是自动发出的),所以它不能分为标题和源文件,这是至关重要的使其可以访问C代码(标题)和汇编(源代码)。

  3. 有没有更好的方法来实现我一直想做的事情?

2 个答案:

答案 0 :(得分:3)

call迫使你假设大多数寄存器被破坏的开销非常高。对于高性能,您需要手动将函数内联到asm中,以便完全优化所有内容

让编译器发出一个独立的定义并调用它只应该考虑那些不是性能关键的代码。你没有说出你在asm写的内容,或者为什么,但我认为它是性能至关重要的。否则你只需用C语言编写它(对于任何特殊指令都有内联asm,我猜?)。

如果你不想手动内联,并且想要在循环中使用这些小的内联C函数,那么你可能会通过在C中编写整个内容来获得更好的性能。这会让编译器优化了更多代码。

用于x86-64的register-arg调用约定很好,但是有很多寄存器被称为clobbered,因此在计算中间的调用会阻止你在寄存器中保存尽可能多的数据。 / p>

  

汇编代码可以通过任何方式使用内联吗?我的意思是在一个   .S文件,而不是内联汇编。

不,内联-asm的反向没有语法。如果有的话,它会是这样的:你告诉编译器输入的寄存器是什么,你想要输出什么寄存器,以及哪些寄存器允许到clobber。

手写asm和编译器输出之间的公共子表达式消除和其他重要优化在没有真正理解手写asm或被处理的编译器的情况下是不可能的它作为源代码,然后发出整个事物的优化版本。

优化将编译器输出内联到asm通常需要调整asm,这就是没有任何程序可以执行此操作的原因。

  

有没有更好的方法来实现我一直在尝试做的事情?

现在您已经在评论中解释了您的目标是什么:在C中制作小包装器以获取您想要使用的特殊说明,而不是相反。

#include <stdint.h>
struct __attribute__((packed)) lgdt_arg {
    uint16_t limit;
    void * base;    // FIXME: always 64bit in long mode, including the x32 ABI where pointers and uintptr_t are 32bit.
                    // In 16bit mode, base is 24bit (not 32), so I guess be careful with that too
                    // you could just make this a uint64_t, since x86 is little-endian.
                    //  The trailing bytes don't matter since the instruction just uses a pointer to the struct.
};

inline void lgdt (const struct lgdt_arg *p) {
    asm volatile ("lgdt %0" : : "m"(*p) : "memory");
}

// Or this kind of construct sometimes gets used to make doubly sure compile-time reordering doesn't happen:
inline void lgdt_v2 (struct lgdt_arg *p) {
    asm volatile ("lgdt %0" : "+m"(*(volatile struct lgdt_arg *)p) :: "memory");
}
// that puts the asm statement into the dependency chain of things affecting the contents of the pointed-to struct, so the compiler is forced to order it correctly.


void set_gdt(unsigned size, char *table) {
  struct lgdt_arg tmp = { size, table };
  lgdt (&tmp);
}

set_gdt compiles to (gcc 5.3 -O3 on godbolt)

    movw    %di, -24(%rsp)
    movq    %rsi, -22(%rsp)
    lgdt -24(%rsp)
    ret

我从未编写过涉及lgdt的代码。使用&#34;内存&#34;可能是一个好主意。像我一样做的clobber确保在编译时没有任何加载/存储重新排序。这将确保它指向的GDT可能在运行LGDT之前完全初始化。 (LIDT相同)。编译器可能会注意到base给内联作为GDT的引用,并确保其内容同步,但我不确定。使用&#34;内存&#34;应该几乎没有任何缺点。在这里破坏。

Linux(内核)在一个或两个指令周围使用这种包装,在asm中编写尽可能少的代码。如果你愿意,可以寻找灵感。

re:你的意见:是的你想要用asm编写你的引导扇区,也许还有一些其他的16位代码,因为gcc的-m16代码是愚蠢的(基本上仍然是32位代码)。

不,除了手动之外,没有办法将C编译器输出内联到asm中。这是正常的和预期的,出于同样的原因,没有优化组装的程序。 (即读取asm源,优化,写入不同的asm源)。

想想这样一个程序必须做什么:它必须理解手写的asm,以便能够在不破坏手写asm的情况下知道它可以改变什么。作为源语言的Asm并没有给予优化器很多工作。

答案 1 :(得分:2)

您链接的答案解释了C99内联函数如何工作,但没有解释为什么定义是那么古怪。相关标准段落是ISO 9899:2011§6.7.4¶6-7(ISO 9899:1999同上):

  

6使用inline函数说明符声明的函数是内联函数。使函数成为内联函数表明对函数的调用尽可能快。 138 )这些建议有效的程度是实施定义的。 139)

     

7任何具有内部链接的函数都可以是内联函数。对于具有外部链接的函数,以下限制适用:如果使用inline函数说明符声明函数,则它也应在同一转换单元中定义。如果翻译单元中函数的所有文件范围声明都包含inline函数说明符而没有extern,那么该翻译单元中的定义是内联   定义。内联定义不提供函数的外部定义,也不禁止另一个转换单元中的外部定义。内联定义提供了外部定义的替代,翻译器可以使用该定义在同一翻译单元中实现对该功能的任何调用。未指定对函数的调用是使用内联定义还是使用外部定义。 140)

           

138)例如,通过使用通常的函数调用机制的替代方法,例如“内联替换”。内联替换不是文本替换,也不是创建新函数。因此,例如,在函数体内使用的宏的扩展使用它在函数体出现时的定义,而不是调用函数的位置;和标识符指的是身体发生范围内的声明。同样,该函数只有一个地址,无论外部定义发生的内联定义的数量如何。

     

139)例如,实现可能永远不会执行内联替换,或者可能只对inline声明范围内的调用执行内联替换。

     

140)由于内联定义与相应的外部定义以及其他翻译单元中的任何其他相应内联定义不同,因此具有静态存储持续时间的所有相应对象在每个定义中也是不同的。

inline的定义如何发挥作用?好吧,如果翻译单元中只存在函数的inline声明(没有externstatic),则不会发出函数代码。但是如果存在没有inlineextern的单个声明,则会发出该函数的代码,即使它被定义为内联函数。此设计方面允许您描述包含内联函数的机器代码的模块,而无需复制实现:

在头文件中,放置内联定义:

fast_things.h

/* TODO: add assembly implementation */
inline int fast_add(int a, int b)
{
    return (a + b);
}

inline int fast_mul(int a, int b)
{
    return (a * b);
}

此标头可以包含在每个翻译模块中,并为fast_addfast_mul提供内联定义。要为这两个生成机器代码,请添加以下文件:

fast_things.c

#include "fast_things.h"
extern inline int fast_add(int, int);
extern inline int fast_mul(int, int);

你可以避免使用一些宏魔法输入所有这些。像这样更改fast_things.h

#ifndef EXTERN_INLINE
#define EXTERN_INLINE_UNDEFINED
#define EXTERN_INLINE inline
#endif
EXTERN_INLINE int fast_add(int a, int b)
{
    return (a + b);
}

EXTERN_INLINE int fast_mul(int a, int b)
{
    return (a * b);
}
#ifdef EXTERN_INLINE_UNDEFINED
#undef EXTERN_INLINE
#undef EXTERN_INLINE_UNDEFINED
#endif

然后fast_things.c变为:

#define EXTERN_INLINE extern inline
#include "fast_things.h"

由于为内联函数发出了代码,因此您可以从程序集中调用它们。但是,您无法在汇编中内联它们,因为汇编程序不会说C。

当你可以合理地确定它们总是被内联时,还有static inline个函数可能更适合你的目的(即小辅助函数)。

GNU汇编程序支持其自定义宏语言中的宏。一种可能性是编写一个自定义预处理器,它采用内联汇编并为C和气体宏发出gcc样式的内联汇编。这应该可以使用sed,m4或awk(按难度的降序排列)。为此,也可能滥用C预处理器stringify(#)运算符;如果你能给我一个具体的例子,我可以试着把东西放在一起。