保持与内联函数的汇编的兼容性

Retaining Compatibility To Assembly With inline Functions

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

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

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

问题:

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

  2. extern inline 和我现在的方法一样好,但它归结为只有一个定义(外部定义是自动发出的),所以它不能分为头文件和源文件,这对于使其可供 C 代码(头文件)和程序集(源代码)访问至关重要。

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

call 的开销迫使您假设大多数寄存器都被破坏了,这是相当高的。为了获得高性能 你需要手动将你的函数内联到 asm 中,这样你就可以完全优化一切

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

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

用于 x86-64 的 register-arg 调用约定很好,但是有很多寄存器被调用破坏了,所以在计算过程中调用会阻止你在寄存器中保留尽可能多的数据.


Can assembly code, by any means, make use of inlining? I mean as in an .S file, not inline assembly.

不,没有内联汇编的反向语法。如果有的话,它会是这样的:你告诉编译器输入在什么寄存器中,你想要输出在什么寄存器中,以及哪些寄存器是 allowed to clobber.

如果没有真正理解手写 asm 的编译器,手写 asm 和编译器输出之间的公共子表达式消除和其他重要优化是不可能的,或将其视为源代码,然后发布整个内容的优化版本。

最佳 将编译器输出内联到 asm 中通常需要对 asm 进行调整,这就是为什么没有任何程序可以执行此操作的原因。


Is there any better method to achieve what I've been trying to do?

现在您已经在评论中解释了您的目标:用 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 的代码。像我一样使用 "memory" clobber 可能是个好主意,以确保任何 loads/stores 都不会在编译时重新排序。这将确保它指向的 GDT 可能在 运行 LGDT 之前完全初始化。 (与 LIDT 相同)。编译器可能会注意到 base 为内联汇编提供了对 GDT 的引用,并确保其内容同步,但我不确定。在这里使用 "memory" 破坏应该没有什么坏处。

Linux(内核)在整个地方使用这种包装器围绕一两条指令,在 asm 中编写尽可能少的代码。如果需要,可以到那里寻找灵感。


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

不,除了手动,没有其他方法可以将 C 编译器输出内联到 asm 中。这是正常的和预期的,出于同样的原因,没有程序可以优化汇编。 (即读取 asm 源代码、优化、编写不同的 asm 源代码)。

想一想这样的程序必须做什么:它必须理解 手写的 asm 才能知道它可以在不破坏手的情况下改变什么-书面汇编作为源语言的 Asm 并没有为优化器提供太多的工作空间。

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

6 A function declared with an inline function specifier is an inline function. Making a function an inline function suggests that calls to the function be as fast as possible.138) The extent to which such suggestions are effective is implementation-defined. 139)

7 Any function with internal linkage can be an inline function. For a function with external linkage, the following restrictions apply: If a function is declared with an inline function specifier, then it shall also be defined in the same translation unit. If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.140)


138) By using, for example, an alternative to the usual function call mechanism, such as ”inline substitution”. Inline substitution is not textual substitution, nor does it create a new function. Therefore, for example, the expansion of a macro used within the body of the function uses the definition it had at the point the function body appears, and not where the function is called; and identifiers refer to the declarations in scope where the body occurs. Likewise, the function has a single address, regardless of the number of inline definitions that occur in addition to the external definition.

139) For example, an implementation might never perform inline substitution, or might only perform inline substitutions to calls in the scope of an inline declaration.

140) Since an inline definition is distinct from the corresponding external definition and from any other corresponding inline definitions in other translation units, all corresponding objects with static storage duration are also distinct in each of the definitions.

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

在您的 header 文件中,放置内联定义:

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);
}

此 header 可以包含在每个翻译模块中,并为 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 和 gas 宏发出 gcc-style 内联汇编。这应该可以使用 sed、m4 或 awk(按难度降序排列)。也可能为此滥用 C 预处理器 stringify (#) 运算符;如果你能给我一个具体的例子,我可以试着把一些东西放在一起。