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

标签 c gcc assembly inline gnu-toolchain

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

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

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

问题:

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

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

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

最佳答案

调用 的开销迫使您假设大多数寄存器都被破坏了,这是相当高的。为了获得高性能,您需要手动将您的函数内联到 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.

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

如果没有真正理解手写 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 的代码。像我一样使用“内存”破坏器可能是个好主意,以确保在编译时不会对任何加载/存储进行重新排序。这将确保它指向的 GDT 在运行 LGDT 之前可能已完全初始化。 (同样适用于 LIDT)。编译器可能会注意到 base 为内联 asm 提供了对 GDT 的引用,并确保其内容同步,但我不确定。在这里使用“内存”破坏应该没有什么坏处。

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


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

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

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

关于c - 保持与内联函数的汇编的兼容性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36283404/

相关文章:

我可以将 Intel 语法的 x86 程序集与 GCC 一起使用吗?

x64 JMP 指令的汇编解码

c - 在其字段中声明指向结构的指针

c - 为什么我的函数没有真正初始化数组?

C - 比较整数时出现段错误

arrays - 使用 C 将一维字符数组复制到二维字符数组

c - 在多进程 scanf 的 MPI 中只接受一次输入并将垃圾值分配给其他?

c - 如何使用 SIMD 向量化和/或并行化让编译器为字符串搜索循环输出更快的代码?

c - 动态生成的代码在错误的地址执行

c++ - 在 RTTI 代码中使用非 RTTI 接口(interface)