Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.2k views
in Technique[技术] by (71.8m points)

c - Embedded broadcasts with intrinsics and assembly

In section 2.5.3 "Broadcasts" of the Intel Architecture Instruction Set Extensions Programming Reference the we learn than AVX512 (and Knights Corner) has

a bit-field to encode data broadcast for some load-op instructions, i.e. instructions that load data from memory and perform some computational or data movement operation.

For example using Intel assembly syntax we can broadcast the scalar at the address stored in rax and then multiplying with the 16 floats in zmm2 and write the result to zmm1 like this

vmulps zmm1, zmm2, [rax] {1to16}

However, there are no intrinsics which can do this. Therefore, with intrinsics the compiler should be able to fold

__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);

to a single instruction

vmulps zmm1, zmm2, [rax] {1to16}

but I have not observed GCC doing this. I found a GCC bug report about this.

I have observed something similar with FMA with GCC. e.g. GCC 4.9 will not collapse _mm256_add_ps(_mm256_mul_ps(areg0,breg0) to a single fma instruction with -Ofast. However, GCC 5.1 does collapse it to a single fma now. At least there are intrinsics to do this with FMA e.g. _mm256_fmadd_ps. But there is no e.g. _mm512_mulbroad_ps(vector,scalar) intrinsic.

GCC may fix this at some point but until then assembly is the only solution.

So my question is how to do this with inline assembly in GCC?

I think I may have come up with the correct syntax (but I am not sure) for GCC inline assembly for the example above.

"vmulps        (%%rax)%{1to16}, %%zmm1, %%zmm2
"

I am really looking for a function like this

static inline __m512 mul_broad(__m512 a, float b) {
    return a*b;
}

where if b is in memory point to in rax it produces

vmulps        (%rax){1to16}, %zmm0, %zmm0
ret

and if b is in xmm1 it produces

vbroadcastss    %xmm1, %zmm1
vmulps          %zmm1, %zmm0, %zmm0
ret

GCC will already do the vbroadcastss-from-register case with intrinsics, but if b is in memory, compiles this to a vbroadcastss from memory.

__m512 mul_broad(__m512 a, float b) {       
    __m512 bb = _mm512_set1_ps(b);
    __m512 ab = _mm512_mul_ps(a,bb);
    return ab;
}

clang will use a broadcast memory operand if b is in memory.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As Peter Cordes notes GCC doesn't let you specify a different template for different constraint alternatives. So instead my solution has the assembler choose the correct instruction according to the operands chosen.

I don't have a version of GCC that supports the ZMM registers, so this following example uses XMM registers and a couple of nonexistent instructions to demonstrate how you can achieve what you're looking for.

typedef __attribute__((vector_size(16))) float v4sf;

v4sf
foo(v4sf a, float b) {
    v4sf ret;
    asm(".ifndef isxmm
"
        ".altmacro
"
        ".macro ifxmm operand, rnum
"
        ".ifc "\operand","%%xmm\rnum"
"
        ".set isxmm, 1
"
        ".endif
"
        ".endm
"
        ".endif
"
        ".set isxmm, 0
"
        ".set regnum, 0
"
        ".rept 8
"
        "ifxmm <%2>, %%regnum
"
        ".set regnum, regnum + 1
"
        ".endr
"
        ".if isxmm
"
        "alt-1 %1, %2, %0
"
        ".else
"
        "alt-2 %1, %2, %0
"
        ".endif
"
        : "=x,x" (ret)
        : "x,x" (a), "x,m" (b));
    return ret;
}


v4sf
bar(v4sf a, v4sf b) {
    return foo(a, b[0]);
}

This example should be compiled with gcc -m32 -msse -O3 and should generate two assembler error messages similar to the following:

t103.c: Assembler messages:
t103.c:24: Error: no such instruction: `alt-2 %xmm0,4(%esp),%xmm0'
t103.c:22: Error: no such instruction: `alt-1 %xmm0,%xmm1,%xmm0'

The basic idea here is the assembler checks to see whether the second operand (%2) is an XMM register or something else, presumably a memory location. Since the GNU assembler doesn't support much in the way of operations on strings, the second operand is compared to every possible XMM register one at a time in a .rept loop. The isxmm macro is used to paste %xmm and a register number together.

For your specific problem you'd probably need to rewrite it something like this:

__m512
mul_broad(__m512 a, float b) {
    __m512 ret;
    __m512 dummy;
    asm(".ifndef isxmm
"
        ".altmacro
"
        ".macro ifxmm operand, rnum
"
        ".ifc "\operand","%%zmm\rnum"
"
        ".set isxmm, 1
"
        ".endif
"
        ".endm
"
        ".endif
"
        ".set isxmm, 0
"
        ".set regnum, 0
"
        ".rept 32
"
        "ifxmm <%[b]>, %%regnum
"
        ".set regnum, regnum + 1
"
        ".endr
"
        ".if isxmm
"
        "vbroadcastss %x[b], %[b]
"
        "vmulps %[a], %[b], %[ret]
"
        ".else
"
        "vmulps %[b] %{1to16%}, %[a], %[ret]
"
        "# dummy = %[dummy]
"
        ".endif
"
        : [ret] "=x,x" (ret), [dummy] "=xm,x" (dummy)
        : [a] "x,xm" (a), [b] "m,[dummy]" (b));
    return ret;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...