2

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

inline问题是我在这些头文件中有很多功能。汇编器无法处理函数,这些函数不是目标文件中的符号(与static inline函数一样),所以我不能使用它们。我已经阅读了这篇文章和这篇非常有价值的文章,并且现在已经掌握了如何使用externstatic结合使用inline,但我不确定如何让C 代码和程序集inline可以访问函数。

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

问题:

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

  2. extern inline与我当前的方法一样好,但它归结为只有一个定义(外部定义是自动发出的),因此它不能分为头文件和源文件,这对于使其可访问 C 代码(头文件)和组装(来源)。

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

4

2 回答 2

3

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

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

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

用于 x86-64 的 register-arg 调用约定很好,但是有很多寄存器是 call-clobbered 的,所以计算中间的调用会阻止你将尽可能多的数据保存在寄存器中。


无论如何,汇编代码可以使用内联吗?我的意思是在 .S 文件中,而不是内联汇编。

不,没有与 inline-asm 相反的语法。如果有,它将类似于:您告诉编译器输入在哪些寄存器中,您希望在哪些寄存器中输出,以及允许破坏哪些寄存器。

如果没有真正理解手写 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 编译为(-O3godbolt 上的 gcc 5.3)

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

我从来没有写过涉及lgdt. 像我一样使用“内存”破坏器可能是一个好主意,以确保在编译时不会重新排序任何加载/存储。这将确保它指向的 GDT 在运行之前完全初始化LGDT。(对于 相同LIDT)。编译器可能会注意到它base为内联汇编提供了对 GDT 的引用,并确保其内容是同步的,但我不确定。在这里只使用“内存”破坏器应该几乎没有缺点。

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


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

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

想想这样一个程序必须做什么:它必须理解手写 asm 才能知道它可以改变什么而不破坏手写 asm。Asm 作为一种源语言并没有给优化器太多的工作。

于 2016-03-29T13:14:59.207 回答
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声明(没有externor static),则不会发出函数的代码。inline但是,如果存在不带或带的单个声明extern,则发出该函数的代码,即使它被定义为内联函数。此设计方面允许您描述包含内联函数的机器代码的模块,而无需复制实现:

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

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_add定义fast_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 样式的内联汇编。这应该可以使用 sed、m4 或 awk(按难度降序排列)。也有可能为此滥用 C 预处理器 stringify ( #) 操作符;如果你能给我一个具体的例子,我可以试着把一些东西放在一起。

于 2016-03-29T21:24:55.790 回答