Uint8 到 mm0 寄存器

Uint8 to mm0 register

我一直在玩 this 演示文稿(幻灯片 41)中的示例。

就我而言,它执行 alpha 混合。

MOVQ mm0, alpha//4 16-b zero-padding α
MOVD mm1, A //move 4 pixels of image A 
MOVD mm2, B //move 4 pixels of image B
PXOR mm3 mm3 //clear mm3 to all zeroes 
//unpack 4 pixels to 4 words
PUNPCKLBW mm1, mm3 // Because B -A could be
PUNPCKLBW mm2, mm3 // negative, need 16 bits
PSUBW mm1, mm2 //(B-A) 
PMULHW mm1, mm0 //(B-A)*fade/256 
PADDW mm1, mm2 //(B-A)*fade + B 
//pack four words back to four bytes
PACKUSWB mm1, mm3

我想用汇编语言重写它。

现在,我有这样的东西:

void fade_mmx(SDL_Surface* im1,SDL_Surface* im2,Uint8 alpha, SDL_Surface* imOut)
{
    int pixelsCount = imOut->w * im1->h;
    
    Uint32 *A = (Uint32*) im1->pixels;
    Uint32 *B = (Uint32*) im2->pixels;
    Uint32 *out = (Uint32*) imOut->pixels;
    Uint32 *end = out + pixelsCount;

    __asm__ __volatile__ (
            "\n\t movd  (%0), %%mm0"
            "\n\t movd  (%1), %%mm1"
            "\n\t movd  (%2), %%mm2"
            "\n\t pxor       %%mm3, %%mm3"
            "\n\t punpcklbw  %%mm3, %%mm1"
            "\n\t punpcklbw  %%mm3, %%mm2"
            "\n\t psubw      %%mm2, %%mm1"
            "\n\t pmulhw     %%mm0, %%mm1"
            "\n\t paddw      %%mm2, %%mm1"
            "\n\t packuswb   %%mm3, %%mm1"
    : : "r" (alpha), "r" (A), "r" (B), "r" (out), "r" (end)
    );
    __asm__("emms" : : );
}

编译时我收到此消息:Error: (%dl) is not a valid base/index expression 关于汇编程序中的第一行。 我怀疑这是因为 alphaUint8,我尝试转换它但后来出现分段错误。在这个例子中,他们谈论的是 4 16-b zero-padding α 我不太清楚。

在复制到 MM reg 之前,您可以使用标量乘以 0x0001000100010001ULLalpha 广播到 64 位。另一种选择是将 movd 的 8 位整数零扩展为 32 位,然后 pshufw 复制它。

您的 asm 也存在各种安全问题。

#include <SDL/SDL.h>
#include <stdint.h>

void fade_mmx(SDL_Surface* im1,SDL_Surface* im2,Uint8 alpha, SDL_Surface* imOut)
{
    int pixelsCount = imOut->w * im1->h;

    Uint32 *A = (Uint32*) im1->pixels;
    Uint32 *B = (Uint32*) im2->pixels;
    Uint32 *out = (Uint32*) imOut->pixels;
    Uint32 *end = out + pixelsCount;

    Uint64 alphas = (Uint64)alpha * 0x0001000100010001ULL;

    __asm__ __volatile__ (
            "\n\t movd  %0, %%mm0"
            "\n\t movd  %1, %%mm1"
            "\n\t movd  %2, %%mm2"
            "\n\t pxor       %%mm3, %%mm3"
            "\n\t punpcklbw  %%mm3, %%mm1"
            "\n\t punpcklbw  %%mm3, %%mm2"
            "\n\t psubw      %%mm2, %%mm1"
            "\n\t pmulhw     %%mm0, %%mm1"
            "\n\t paddw      %%mm2, %%mm1"
            "\n\t packuswb   %%mm3, %%mm1"
    : // you're probably going to want an "=m"(*something) memory output here
    : "r" (alphas), "m" (*A), "m" (*B), "r" (out), "r" (end)
    : "mm0", "mm1", "mm2", "mm3");
    __asm__("emms" : : );
}

如果编译器知道所有输入和输出,而不是依赖于 "memory" 破坏,则 asm 语句不需要 volatile。 (像这里一样,没有输出,只读取作为输入操作数的寄存器和内存。)

对于 32 位代码,将 "r"(alphas) 替换为 "m"(alphas)。或者使用 "rm"(alphas) 让编译器选择。 (但是对于 32 位,使用 pshufw 肯定更好,而不是让编译器将 64 位乘法结果存储为 2 个 32 位的一半,然后在使用 movq 重新加载它时遭受存储转发停顿。内在函数会留下决定使用 _mm_set1_epi16(alpha) 给编译器,尽管你只在循环外执行一次)。

请注意,我还添加了必要的破坏列表,并将包含您取消引用的指针的寄存器操作数替换为引用您取消引用的内存的内存操作数,从而允许 gcc 推断您访问的内存

请注意,如果您不解决这些问题,gcc 将不愉快并且您的代码的行为将是未定义的,可能会以神秘且难以调试的方式失败。除非您完全了解自己在做什么,否则不要使用内联汇编。考虑使用内部函数作为更安全且可能更有效的替代方法。 (https://gcc.gnu.org/wiki/DontUseInlineAsm).

带有 __m128i 向量的 SSE2 可以很容易地一次处理 4 个像素,而不是 2 或 1 浪费一半的 pack 吞吐量,因为用零打包。 (使用 punpckhbw 补充 punpcklbw 来设置)。 MMX 已经过时,以至于现代 CPU 对某些指令的 MMX 版本的吞吐量低于等效的 128 位 SSE2 XMM 指令。

您的问题是您试图将 alpha 值用作地址而不是值。 movd (%0), %%mm0 指令表示使用 %0 作为内存中的一个位置。所以你说要加载 alpha 指向的值而不是它的值。使用 movd %0, %%mm0 可以解决该问题,但是您 运行 会遇到这样的问题,即您的 alpha 值只有 8 位类型,并且它需要是 32 位类型它与 MOVD 指令一起工作。您可以解决该问题,事实上 alpha 值需要乘以 256 并广播到目标寄存器的所有 4 个 16 位字,以便您的算法通过将其乘以 0x0100010001000100ULL 并使用MOVQ 指令。

但是,您根本不需要 MOVD/MOVQ 说明。您可以通过使用如下代码指定 y 约束,让编译器将值加载到 MMX 寄存器中:

typedef unsigned pixel;

static inline pixel
fade_pixel_mmx_asm(pixel p1, pixel p2, unsigned fade) {
    asm("punpcklbw %[zeros], %[p1]\n\t"
        "punpcklbw %[zeros], %[p2]\n\t"
        "psubw     %[p2], %[p1]\n\t"
        "pmulhw    %[fade], %[p1]\n\t"
        "paddw     %[p2], %[p1]\n\t"
        "packuswb  %[zeros], %[p1]"
        : [p1] "+&y" (p1), [p2] "+&y" (p2)
        : [fade] "y" (fade * 0x0100010001000100ULL), [zeros] "y" (0));
    return p1;
}

您会注意到这里不需要 clobber 列表,因为没有使用不是由编译器分配的寄存器,也没有编译器需要知道的其他副作用。我省略了必要的 EMMS 指令,因为您不想在每个像素上执行。您需要在混合两个表面的循环之后插入一个 asm("emms"); 语句。

更好的是,您根本不需要使用内联汇编。您可以改用内部函数,而不必担心使用内联汇编的所有陷阱:

#include <mmintrin.h>

static inline pixel
fade_pixel_mmx_intrin(pixel p1, pixel p2, unsigned fade) {
    __m64 zeros = (__m64) 0ULL;
    __m#64 mfade = (__m64) (fade * 0x0100010001000100ULL);
    __m64 mp1 = _m_punpcklbw((__m64) (unsigned long long) p1, zeros);
    __m64 mp2 = _m_punpcklbw((__m64) (unsigned long long) p2, zeros);

    __m64 ret;
    ret = _m_psubw(mp1, mp2);
    ret = _m_pmulhw(ret, mfade);
    ret = _m_paddw(ret, mp2);
    ret = _m_packuswb(ret, zeros);

    return (unsigned long long) ret;
}
    

与前面的示例类似,您需要在循环后调用 _m_empty() 以生成必要的 EMMS 指令。

您还应该认真考虑只用纯 C 语言编写例程。自动向量化器现在非常好,编译器使用现代 SIMD 指令生成的代码可能比您尝试使用古老的 MMX 指令生成的代码更好.例如,这段代码:

static inline unsigned
fade_component(unsigned c1, unsigned c2, unsigned fade) {
    return c2  + (((int) c1 - (int) c2) * fade) / 256;
}

void
fade_blend(pixel *dest, pixel *src1, pixel *src2, unsigned char fade,
           unsigned len) {
    unsigned char *d = (unsigned char *) dest;
    unsigned char *s1 = (unsigned char *) src1;
    unsigned char *s2 = (unsigned char *) src2;
    unsigned i;
    for (i = 0; i < len * 4; i++) {
        d[i] = fade_component(s1[i], s2[i], fade);
    }
}

使用 GCC 10.2 和 -O3 上述代码生成的汇编代码使用 128 位 XMM 寄存器并在其内部循环中一次混合 4 个像素:

    movdqu  xmm5, XMMWORD PTR [rdx+rax]
    movdqu  xmm1, XMMWORD PTR [rsi+rax]
    movdqa  xmm6, xmm5
    movdqa  xmm0, xmm1
    punpckhbw       xmm1, xmm3
    punpcklbw       xmm6, xmm3
    punpcklbw       xmm0, xmm3
    psubw   xmm0, xmm6
    movdqa  xmm6, xmm5
    punpckhbw       xmm6, xmm3
    pmullw  xmm0, xmm2
    psubw   xmm1, xmm6
    pmullw  xmm1, xmm2
    psrlw   xmm0, 8
    pand    xmm0, xmm4
    psrlw   xmm1, 8
    pand    xmm1, xmm4
    packuswb        xmm0, xmm1
    paddb   xmm0, xmm5
    movups  XMMWORD PTR [rdi+rax], xmm0

最后,即使是 C 代码的非矢量化版本也可能接近最佳,因为代码非常简单,无论混合的实现方式如何,您都可能会受到内存限制。