gpt4 book ai didi

c - 内联汇编导致没有前缀的错误

转载 作者:太空宇宙 更新时间:2023-11-04 03:37:11 25 4
gpt4 key购买 nike

你好,

因此,我正在优化我为正在开发的简单操作系统编写的一些函数。此函数 putpixel() 目前看起来像这样(以防我的程序集不清楚或错误):

uint32_t loc  = (x*pixel_w)+(y*pitch);
vidmem[loc] = color & 255;
vidmem[loc+1] = (color >> 8) & 255;
vidmem[loc+2] = (color >> 16) & 255;

这需要一点解释。首先,loc 是我要写入显存中的像素索引。 X 和 Y 坐标传递给函数。然后,我们将 X 乘以以字节为单位的像素宽度(在本例中为 3),将 Y 乘以每行中的字节数。可以找到更多信息here .

vidmem 是一个全局变量,一个指向显存的uint8_t指针。

话虽如此,任何熟悉位运算的人都应该能够相当轻松地弄清楚 putpixel() 的工作原理。

现在,这是我的程序集。请注意,它尚未经过测试,甚至可能会变慢或根本无法正常工作。这个问题是关于如何编译的。

我已经将 loc 定义之后的所有内容替换为:

__asm(
"push %%rdi;"
"push %%rbx;"
"mov %0, %%rdi;"
"lea %1, %%rbx;"
"add %%rbx, %%rdi;"
"pop %%rbx;"
"mov %2, %%rax;"
"stosb;"
"shr $8, %%rax;"
"stosb;"
"shr $8, %%rax;"
"stosb;"
"pop %%rdi;" : :
"r"(loc), "r"(vidmem), "r"(color)
);

当我编译它时,clang 会针对每个 push 指令给我这个错误: unknown use of instruction mnemonic without a size suffix

因此,当我看到该错误时,我认为这与我遗漏了 GAS 后缀有关(无论如何,这应该是隐式决定的)。但是当我添加“l”后缀(我所有的变量都是uint32_ts)时,我得到了同样的错误!我不太确定是什么原因造成的,我们将不胜感激。提前致谢!

最佳答案

通过将 vidmem 加载到存储之前的局部变量中,您可能可以使 C 版本的编译器输出更加高效。实际上,它不能假定存储不为 vidmem 设置别名,因此它会在每个字节存储之前重新加载指针。嗯,这确实让 gcc 4.9.2 避免重新加载 vidmem,但它仍然会生成一些讨厌的代码。 clang 3.5 稍微好一些。

实现我在对您的回答的评论中所说的(stos 是 3 uops,而 mov 是 1):

#include <stdint.h>

extern uint8_t *vidmem;
void putpixel_asm_peter(uint32_t color, uint32_t loc)
{
// uint32_t loc = (x*pixel_w)+(y*pitch);
__asm( "\n"
"\t movb %b[col], (%[ptr])\n"
"\t shrl $8, %[col];\n"
"\t movw %w[col], 1(%[ptr]);\n"
: [col] "+r" (color), "=m" (vidmem[loc])
: [ptr] "r" (vidmem+loc)
:
);
}

编译成一个非常有效的实现:

gcc -O3 -S -o- putpixel.c 2>&1 | less  # (with extra lines removed)

putpixel_asm_peter:
movl %esi, %esi
addq vidmem(%rip), %rsi
#APP
movb %dil, (%rsi)
shrl $8, %edi;
movw %di, 1(%rsi);
#NO_APP
ret

所有这些指令在 Intel CPU 上解码为单个 uop。 (存储可以微融合,因为它们使用单​​寄存器寻址模式。)movl %esi, %esi 将高位 32 归零,因为调用者可能已经生成了具有 64 位的函数 arg在 %rsi 的高 32 位指示剩余垃圾。您的版本可以通过首先使用约束来请求所需寄存器中的值来保存一些指令,但这仍然比 stos

另请注意我是如何让编译器负责将 loc 添加到 vidmem 的。您可以在自己的代码中更有效地完成它,使用 lea 将添加与移动结合起来。但是,如果编译器想在循环中使用它时变得聪明,它可以递增指针而不是递增地址。最后,这意味着相同的代码将适用于 32 位和 64 位。 %[ptr] 在 64 位模式下是 64 位 reg,但在 32 位模式下是 32 位 reg。因为我不需要对它做任何数学运算,所以它很管用。

我使用 =m 输出约束来告诉编译器我们在内存中写入的位置。 (我应该将指针转换到 struct { char a[3]; } 或其他东西,以告诉 gcc 它实际写入了多少内存,根据“Clobbers”末尾的提示the gcc manual 中的部分)

我还使用color作为输入/输出约束来告诉编译器我们修改了它。如果它被内联,并且后面的代码预计仍会在寄存器中找到 color 的值,我们就会遇到问题。在函数中使用它意味着 color 已经是调用者值的 tmp 副本,因此编译器将知道它需要丢弃旧颜色。使用两个只读输入在循环中调用它可能会稍微更有效:一个用于 color,一个用于 color >> 8

请注意,我可以将约束写为

    : [col] "+r" (color), [memref] "=m" (vidmem[loc])
:
:

但是使用 %[memref]1 %[memref] 生成所需的地址会导致 gcc 发出

    movl    %esi, %esi
movq vidmem(%rip), %rax
# APP
movb %edi, (%rax,%rsi)
shrl $8, %edi;
movw %edi, 1 (%rax,%rsi);

双寄存器寻址模式意味着存储指令不能微融合(至少在 Sandybridge 和更高版本上)。

不过,您甚至不需要内联汇编来获得像样的代码:

void putpixel_cast(uint32_t color, uint32_t loc)
{
// uint32_t loc = (x*pixel_w)+(y*pitch);
typeof(vidmem) vmem = vidmem;
vmem[loc] = color & 255;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
*(uint16_t *)(vmem+loc+1) = color >> 8;
#else
vmem[loc+1] = (color >> 8) & 255; // gcc sucks at optimizing this for little endian :(
vmem[loc+2] = (color >> 16) & 255;
#endif
}

编译为(gcc 4.9.2 和 clang 3.5 给出相同的输出):

    movq    vidmem(%rip), %rax
movl %esi, %esi
movb %dil, (%rax,%rsi)
shrl $8, %edi
movw %di, 1(%rax,%rsi)
ret

这只比我们使用内联 asm 得到的效率低一点点,如果内联到循环中,优化器应该更容易优化。

整体表现

在循环中调用它可能是一个错误。将多个像素组合在一个寄存器(尤其是 vector 寄存器)中,然后一次写入会更有效。或者,进行 4 字节写入,与前一次写入的最后一个字节重叠,直到到达末尾并且必须保留最后一个 block 3 之后的字节。

参见 http://agner.org/optimize/有关优化 C 和 asm 的更多信息。该链接和其他链接可在 https://stackoverflow.com/tags/x86/info 找到.

关于c - 内联汇编导致没有前缀的错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31689292/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com