导致无前缀的内联汇编错误

问题描述 投票:1回答:2

你好,

因此,我正在优化我为正在开发的简单操作系统编写的某些功能。该函数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_t s)时,我遇到了相同的错误!我不太确定是什么原因引起的,我们将不胜感激。预先感谢!

c assembly clang inline-assembly gas
2个回答
1
投票

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

执行我在对您的回答的评论中所说的内容(stos为3微妙,而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,而左垃圾在[ C0]。您的版本本来可以通过使用约束条件首先在所需的寄存器中询问值来保存一些指令,但是仍然比%rsi

[还要注意我如何让编译器负责将stos添加到loc。您可以通过vidmem来将加号与移动结合起来,从而更有效地完成操作。但是,如果编译器希望在循环中使用它时变得更聪明,则可以增加指针而不是地址。最后,这意味着相同的代码将适用于32位和64位。 lea在64位模式下为64位寄存器,但在32位模式下为32位寄存器。由于我不必对此进行任何数学运算,因此它可以正常工作。

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

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

注意,我可以将约束写为:>

color >> 8

但是使用 : [col] "+r" (color), [memref] "=m" (vidmem[loc]) : : %[memref]生成所需的地址将导致gcc发出

1 %[memref]

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

不过,您甚至不需要内联汇编就可以获得不错的代码:
    movl    %esi, %esi
    movq    vidmem(%rip), %rax
# APP
    movb %edi, (%rax,%rsi)
    shrl $8, %edi;
    movw %edi, 1 (%rax,%rsi);

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

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
}

这仅比我们通过内联汇编获得的效率低一点,如果内联到循环中,优化程序应该更容易优化它。

整体表现

循环调用此函数可能是一个错误。将多个像素合并到一个寄存器(尤其是矢量寄存器)中,然后一次写入所有像素将更加有效。或者,执行4字节写操作,与前一次写操作的最后一个字节重叠,直到到达结尾并必须保留3个最后一块之后的字节。]

请参阅 movq vidmem(%rip), %rax movl %esi, %esi movb %dil, (%rax,%rsi) shrl $8, %edi movw %di, 1(%rax,%rsi) ret 了解有关优化C和asm的更多内容。该链接和其他链接可以在http://agner.org/optimize/中找到。

找到了问题!

[在很多地方,但是主要的是https://stackoverflow.com/tags/x86/info。我以为它会通过地址,但是会导致错误。在将其称为双字之后,它可以完美地工作。我还不得不将其他约束更改为“ m”,并且最终得到了此结果(经过一些优化):

vidmem

感谢所有在评论中回答的人!


0
投票

找到了问题!

[在很多地方,但是主要的是https://stackoverflow.com/tags/x86/info。我以为它会通过地址,但是会导致错误。在将其称为双字之后,它可以完美地工作。我还不得不将其他约束更改为“ m”,并且最终得到了此结果(经过一些优化):

vidmem
© www.soinside.com 2019 - 2024. All rights reserved.