July 12, 2013
Manu:

> This is interesting. I didn't know about this.

I have taken a look at this page:
https://github.com/ispc/ispc

There is a free compiler binary for various operating systems:
http://ispc.github.io/downloads.html

I have tried the Windows compiler on some of the given examples of code, and it works! And the resulting asm is excellent. Normal compilers for the usual languages aren't able to do produce not even nearly as good asm.

To try the code of the examples I compile like this:

ispc.exe --emit-asm stencil.ispc -o stencil.s

Or even like this to see the AVX2 asm instructions:

ispc.exe --target=avx2 --emit-asm stencil.ispc -o stencil.s



As example it compiles a function like this:


static void
stencil_step(uniform int x0, uniform int x1,
             uniform int y0, uniform int y1,
             uniform int z0, uniform int z1,
             uniform int Nx, uniform int Ny, uniform int Nz,
             uniform const float coef[4], uniform const float vsq[],
             uniform const float Ain[], uniform float Aout[]) {
    const uniform int Nxy = Nx * Ny;

    foreach (z = z0 ... z1, y = y0 ... y1, x = x0 ... x1) {
        int index = (z * Nxy) + (y * Nx) + x;
#define A_cur(x, y, z) Ain[index + (x) + ((y) * Nx) + ((z) * Nxy)]
#define A_next(x, y, z) Aout[index + (x) + ((y) * Nx) + ((z) * Nxy)]
        float div = coef[0] * A_cur(0, 0, 0) +
            coef[1] * (A_cur(+1, 0, 0) + A_cur(-1, 0, 0) +
                       A_cur(0, +1, 0) + A_cur(0, -1, 0) +
                       A_cur(0, 0, +1) + A_cur(0, 0, -1)) +
            coef[2] * (A_cur(+2, 0, 0) + A_cur(-2, 0, 0) +
                       A_cur(0, +2, 0) + A_cur(0, -2, 0) +
                       A_cur(0, 0, +2) + A_cur(0, 0, -2)) +
            coef[3] * (A_cur(+3, 0, 0) + A_cur(-3, 0, 0) +
                       A_cur(0, +3, 0) + A_cur(0, -3, 0) +
                       A_cur(0, 0, +3) + A_cur(0, 0, -3));

        A_next(0, 0, 0) = 2 * A_cur(0, 0, 0) - A_next(0, 0, 0) +
            vsq[index] * div;
    }
}



To asm like (using SSE4):


	pshufd	$0, %xmm7, %xmm9        # xmm9 = xmm7[0,0,0,0]
	cmpl	156(%rsp), %r8d         # 4-byte Folded Reload
	movups	(%r14,%rdi), %xmm0
	mulps	%xmm6, %xmm5
	pshufd	$0, %xmm2, %xmm2        # xmm2 = xmm2[0,0,0,0]
	movq	424(%rsp), %rdx
	movups	(%rdx,%r15), %xmm6
	mulps	%xmm3, %xmm2
	pshufd	$0, %xmm10, %xmm3       # xmm3 = xmm10[0,0,0,0]
	mulps	%xmm11, %xmm3
	addps	%xmm2, %xmm3
	addps	%xmm5, %xmm3
	addps	%xmm4, %xmm0
	movslq	%eax, %rax
	movups	(%r14,%rax), %xmm2
	addps	%xmm0, %xmm2
	movslq	%ebp, %rax
	movups	(%r14,%rax), %xmm0
	addps	%xmm2, %xmm0
	mulps	%xmm9, %xmm0
	addps	%xmm3, %xmm0
	mulps	%xmm6, %xmm0
	addps	%xmm1, %xmm0
	movups	%xmm0, (%r11,%r15)
	jl	.LBB0_5
.LBB0_6:                                # %partial_inner_all_outer
                                        #   in Loop: Header=BB0_4 Depth=2
	movq	%r13, %rbp
	movl	156(%rsp), %r13d        # 4-byte Reload
	movq	424(%rsp), %r9
	cmpl	64(%rsp), %r8d          # 4-byte Folded Reload
	movq	%r11, %rbx
	jge	.LBB0_257
# BB#7:                                 # %partial_inner_only
                                        #   in Loop: Header=BB0_4 Depth=2
	movd	%r8d, %xmm0
	movl	84(%rsp), %r15d         # 4-byte Reload
	imull	400(%rsp), %r15d
	pshufd	$0, %xmm0, %xmm0        # xmm0 = xmm0[0,0,0,0]
	paddd	.LCPI0_1(%rip), %xmm0
	movdqa	%xmm8, %xmm1
	pcmpgtd	%xmm0, %xmm1
	movmskps	%xmm1, %edi
	addl	68(%rsp), %r15d         # 4-byte Folded Reload
	leal	(%r8,%r15), %r10d



Or using AVX2 (this not exactly the equievalent piece of code) (The asm generates with AVX2 is usually significant shorter):


	movslq	%edx, %rdx
	vaddps	(%rax,%rdx), %ymm5, %ymm5
	movl	52(%rsp), %edx          # 4-byte Reload
	leal	(%rdx,%r15), %edx
	movslq	%edx, %rdx
	vaddps	(%rax,%rdx), %ymm5, %ymm5
	vaddps	%ymm11, %ymm6, %ymm9
	vaddps	%ymm10, %ymm8, %ymm10
	vmovups	(%rax,%rdi), %ymm12
	addl	$32, %r15d
	vmovups	(%r11,%rcx), %ymm6
	movq	400(%rsp), %rdx
	vbroadcastss	12(%rdx), %ymm7
	vbroadcastss	8(%rdx), %ymm8
	vbroadcastss	(%rdx), %ymm11
	vaddps	%ymm12, %ymm10, %ymm10
	cmpl	48(%rsp), %r14d         # 4-byte Folded Reload
	vbroadcastss	4(%rdx), %ymm12
	vmulps	%ymm9, %ymm12, %ymm9
	vfmadd213ps	%ymm9, %ymm3, %ymm11
	vfmadd213ps	%ymm11, %ymm8, %ymm10
	vfmadd213ps	%ymm10, %ymm5, %ymm7
	vfmadd213ps	%ymm4, %ymm6, %ymm7
	vmovups	%ymm7, (%r8,%rcx)
	jl	.LBB0_8
# BB#4:                                 # %partial_inner_all_outer.us
                                        #   in Loop: Header=BB0_7 Depth=2
	cmpl	40(%rsp), %r14d         # 4-byte Folded Reload
	movq	400(%rsp), %r10
	jge	.LBB0_6
# BB#5:                                 # %partial_inner_only.us
                                        #   in Loop: Header=BB0_7 Depth=2
	vmovd	%r14d, %xmm3
	vbroadcastss	%xmm3, %ymm3
	movl	100(%rsp), %ecx         # 4-byte Reload
	leal	(%rcx,%r15), %ecx
	vpaddd	%ymm2, %ymm3, %ymm3




Sometimes it gives performance warnings:

rt.ispc:257:9: Performance Warning: Scatter required to store value.
        image[offset] = ray.maxt;
        ^^^^^^^^^^^^^

rt.ispc:258:9: Performance Warning: Scatter required to store value.
        id[offset] = ray.hitId;
        ^^^^^^^^^^


A a bit larger example with this function from a little ray-tracer:


static bool TriIntersect(const uniform Triangle &tri, Ray &ray) {
    uniform float3 p0 = { tri.p[0][0], tri.p[0][1], tri.p[0][2] };
    uniform float3 p1 = { tri.p[1][0], tri.p[1][1], tri.p[1][2] };
    uniform float3 p2 = { tri.p[2][0], tri.p[2][1], tri.p[2][2] };
    uniform float3 e1 = p1 - p0;
    uniform float3 e2 = p2 - p0;

    float3 s1 = Cross(ray.dir, e2);
    float divisor = Dot(s1, e1);
    bool hit = true;

    if (divisor == 0.)
        hit = false;
    float invDivisor = 1.f / divisor;

    // Compute first barycentric coordinate
    float3 d = ray.origin - p0;
    float b1 = Dot(d, s1) * invDivisor;
    if (b1 < 0. || b1 > 1.)
        hit = false;

    // Compute second barycentric coordinate
    float3 s2 = Cross(d, e1);
    float b2 = Dot(ray.dir, s2) * invDivisor;
    if (b2 < 0. || b1 + b2 > 1.)
        hit = false;

    // Compute _t_ to intersection point
    float t = Dot(e2, s2) * invDivisor;
    if (t < ray.mint || t > ray.maxt)
        hit = false;

    if (hit) {
        ray.maxt = t;
        ray.hitId = tri.id;
    }
    return hit;
}


The (more or less) complete asm with AVX2 for that function:


"TriIntersect___REFs[_c_unTriangle]REFs[vyRay]": # @"TriIntersect___REFs[_c_unTriangle]REFs[vyRay]"
# BB#0:                                 # %allocas
    subq    $248, %rsp
    vmovaps %xmm15, 80(%rsp)        # 16-byte Spill
    vmovaps %xmm14, 96(%rsp)        # 16-byte Spill
    vmovaps %xmm13, 112(%rsp)       # 16-byte Spill
    vmovaps %xmm12, 128(%rsp)       # 16-byte Spill
    vmovaps %xmm11, 144(%rsp)       # 16-byte Spill
    vmovaps %xmm10, 160(%rsp)       # 16-byte Spill
    vmovaps %xmm9, 176(%rsp)        # 16-byte Spill
    vmovaps %xmm8, 192(%rsp)        # 16-byte Spill
    vmovaps %xmm7, 208(%rsp)        # 16-byte Spill
    vmovaps %xmm6, 224(%rsp)        # 16-byte Spill
    vmovss  (%rcx), %xmm0
    vmovss  16(%rcx), %xmm2
    vinsertps   $16, 4(%rcx), %xmm0, %xmm0
    vinsertps   $32, 8(%rcx), %xmm0, %xmm0
    vinsertf128 $1, %xmm0, %ymm0, %ymm10
    vmovaps (%rdx), %ymm0
    vmovaps 32(%rdx), %ymm3
    vmovaps 64(%rdx), %ymm1
    vmovaps 96(%rdx), %ymm9
    vpbroadcastd    .LCPI1_0(%rip), %ymm12
    vxorps  %ymm4, %ymm4, %ymm4
    vpermps %ymm10, %ymm4, %ymm4
    vsubps  %ymm4, %ymm0, %ymm0
    vpermps %ymm10, %ymm12, %ymm4
    vsubps  %ymm4, %ymm3, %ymm13
    vmovups %ymm13, (%rsp)          # 32-byte Folded Spill
    vpbroadcastd    .LCPI1_1(%rip), %ymm8
    vpermps %ymm10, %ymm8, %ymm3
    vsubps  %ymm3, %ymm1, %ymm6
    vmovss  32(%rcx), %xmm1
    vinsertps   $16, 36(%rcx), %xmm1, %xmm1
    vinsertps   $32, 40(%rcx), %xmm1, %xmm1
    vinsertf128 $1, %xmm0, %ymm1, %ymm1
    vsubps  %ymm10, %ymm1, %ymm15
    vbroadcastss    %xmm15, %ymm3
    vmovups %ymm3, 32(%rsp)         # 32-byte Folded Spill
    vmovaps 128(%rdx), %ymm11
    vpermps %ymm15, %ymm12, %ymm5
    vmulps  %ymm3, %ymm11, %ymm1
    vmovaps %ymm3, %ymm4
    vmovaps %ymm5, %ymm7
    vfmsub213ps %ymm1, %ymm9, %ymm7
    vinsertps   $16, 20(%rcx), %xmm2, %xmm1
    vinsertps   $32, 24(%rcx), %xmm1, %xmm1
    vinsertf128 $1, %xmm0, %ymm1, %ymm1
    vsubps  %ymm10, %ymm1, %ymm1
    vpermps %ymm1, %ymm12, %ymm14
    vpermps %ymm1, %ymm8, %ymm12
    vmulps  %ymm6, %ymm14, %ymm3
    vmovaps %ymm13, %ymm2
    vfmsub213ps %ymm3, %ymm12, %ymm2
    vbroadcastss    %xmm1, %ymm1
    vmulps  %ymm0, %ymm12, %ymm3
    vmovaps %ymm6, %ymm13
    vfmsub213ps %ymm3, %ymm1, %ymm13
    vmulps  %ymm13, %ymm11, %ymm3
    vmovaps %ymm2, %ymm10
    vfmadd213ps %ymm3, %ymm9, %ymm10
    vpermps %ymm15, %ymm8, %ymm8
    vmulps  %ymm8, %ymm9, %ymm3
    vmovaps 160(%rdx), %ymm9
    vmulps  %ymm5, %ymm9, %ymm15
    vfmsub213ps %ymm15, %ymm8, %ymm11
    vmovaps %ymm4, %ymm15
    vfmsub213ps %ymm3, %ymm9, %ymm15
    vmulps  %ymm15, %ymm14, %ymm4
    vmovaps %ymm11, %ymm3
    vfmadd213ps %ymm4, %ymm1, %ymm3
    vfmadd213ps %ymm3, %ymm7, %ymm12
    vmovups (%rsp), %ymm4           # 32-byte Folded Reload
    vmulps  %ymm4, %ymm15, %ymm3
    vfmadd213ps %ymm3, %ymm0, %ymm11
    vfmadd213ps %ymm11, %ymm6, %ymm7
    vmulps  %ymm4, %ymm1, %ymm1
    vfmsub213ps %ymm1, %ymm14, %ymm0
    vbroadcastss    .LCPI1_2(%rip), %ymm1
    vrcpps  %ymm12, %ymm3
    vmovaps %ymm12, %ymm4
    vfnmadd213ps    %ymm1, %ymm3, %ymm4
    vmulps  %ymm4, %ymm3, %ymm4
    vxorps  %ymm14, %ymm14, %ymm14
    vcmpeqps    %ymm14, %ymm12, %ymm1
    vcmpunordps %ymm14, %ymm12, %ymm3
    vorps   %ymm1, %ymm3, %ymm11
    vmulps  %ymm13, %ymm5, %ymm1
    vmulps  %ymm4, %ymm7, %ymm5
    vmovups 32(%rsp), %ymm3         # 32-byte Folded Reload
    vfmadd213ps %ymm1, %ymm3, %ymm2
    vbroadcastss    .LCPI1_3(%rip), %ymm1
    vcmpnleps   %ymm1, %ymm5, %ymm3
    vcmpnleps   %ymm5, %ymm14, %ymm6
    vorps   %ymm3, %ymm6, %ymm6
    vbroadcastss    .LCPI1_0(%rip), %ymm3
    vblendvps   %ymm11, %ymm14, %ymm3, %ymm7
    vfmadd213ps %ymm10, %ymm0, %ymm9
    vmovaps (%r8), %ymm3
    vmovmskps   %ymm3, %eax
    leaq    352(%rdx), %r8
    cmpl    $255, %eax
    vmulps  %ymm9, %ymm4, %ymm9
    vaddps  %ymm9, %ymm5, %ymm10
    vmovups 320(%rdx), %ymm5
    vfmadd213ps %ymm2, %ymm8, %ymm0
    vblendvps   %ymm6, %ymm14, %ymm7, %ymm6
    vpcmpeqd    %ymm2, %ymm2, %ymm2
    vcmpnleps   %ymm1, %ymm10, %ymm1
    vcmpnleps   %ymm9, %ymm14, %ymm7
    vorps   %ymm1, %ymm7, %ymm1
    vblendvps   %ymm1, %ymm14, %ymm6, %ymm6
    vmulps  %ymm0, %ymm4, %ymm1
    vcmpnleps   352(%rdx), %ymm1, %ymm0
    vcmpnleps   %ymm1, %ymm5, %ymm4
    vorps   %ymm0, %ymm4, %ymm0
    vblendvps   %ymm0, %ymm14, %ymm6, %ymm0
    vpcmpeqd    %ymm14, %ymm0, %ymm4
    vpxor   %ymm2, %ymm4, %ymm2
    je  .LBB1_1
# BB#4:                                 # %some_on
    vpand   %ymm3, %ymm2, %ymm2
.LBB1_1:                                # %all_on
    vmovmskps   %ymm2, %eax
    testl   %eax, %eax
    je  .LBB1_3
# BB#2:                                 # %safe_if_run_true356
    vmaskmovps  %ymm1, %ymm2, (%r8)
    vpbroadcastd    48(%rcx), %ymm1
    vmaskmovps  %ymm1, %ymm2, 384(%rdx)
.LBB1_3:                                # %safe_if_after_true
    vmovaps 224(%rsp), %xmm6        # 16-byte Reload
    vmovaps 208(%rsp), %xmm7        # 16-byte Reload
    vmovaps 192(%rsp), %xmm8        # 16-byte Reload
    vmovaps 176(%rsp), %xmm9        # 16-byte Reload
    vmovaps 160(%rsp), %xmm10       # 16-byte Reload
    vmovaps 144(%rsp), %xmm11       # 16-byte Reload
    vmovaps 128(%rsp), %xmm12       # 16-byte Reload
    vmovaps 112(%rsp), %xmm13       # 16-byte Reload
    vmovaps 96(%rsp), %xmm14        # 16-byte Reload
    vmovaps 80(%rsp), %xmm15        # 16-byte Reload
    addq    $248, %rsp
    ret


Using SSE4 the function asm starts like this:

"TriIntersect___REFs[_c_unTriangle]REFs[vyRay]": # @"TriIntersect___REFs[_c_unTriangle]REFs[vyRay]"
# BB#0:                                 # %allocas
	subq	$248, %rsp
	movaps	%xmm15, 80(%rsp)        # 16-byte Spill
	movaps	%xmm14, 96(%rsp)        # 16-byte Spill
	movaps	%xmm13, 112(%rsp)       # 16-byte Spill
	movaps	%xmm12, 128(%rsp)       # 16-byte Spill
	movaps	%xmm11, 144(%rsp)       # 16-byte Spill
	movaps	%xmm10, 160(%rsp)       # 16-byte Spill
	movaps	%xmm9, 176(%rsp)        # 16-byte Spill
	movaps	%xmm8, 192(%rsp)        # 16-byte Spill
	movaps	%xmm7, 208(%rsp)        # 16-byte Spill
	movaps	%xmm6, 224(%rsp)        # 16-byte Spill
	movss	(%rcx), %xmm1
	movss	16(%rcx), %xmm0
	insertps	$16, 4(%rcx), %xmm1
	insertps	$32, 8(%rcx), %xmm1
	movss	32(%rcx), %xmm7
	insertps	$16, 36(%rcx), %xmm7
	insertps	$32, 40(%rcx), %xmm7
	subps	%xmm1, %xmm7
	pshufd	$-86, %xmm1, %xmm2      # xmm2 = xmm1[2,2,2,2]
	pshufd	$85, %xmm1, %xmm3       # xmm3 = xmm1[1,1,1,1]
	movaps	(%rdx), %xmm4
	movaps	16(%rdx), %xmm12
	movaps	32(%rdx), %xmm5
	movaps	48(%rdx), %xmm11
	subps	%xmm3, %xmm12
	subps	%xmm2, %xmm5
	movaps	%xmm5, 32(%rsp)         # 16-byte Spill
	pshufd	$0, %xmm1, %xmm2        # xmm2 = xmm1[0,0,0,0]
	subps	%xmm2, %xmm4
	pshufd	$85, %xmm7, %xmm3       # xmm3 = xmm7[1,1,1,1]
	movdqa	%xmm3, 48(%rsp)         # 16-byte Spill
	movaps	%xmm11, %xmm2
	mulps	%xmm3, %xmm2
	movaps	%xmm3, %xmm6
	pshufd	$-86, %xmm7, %xmm3      # xmm3 = xmm7[2,2,2,2]
	movdqa	%xmm3, 64(%rsp)         # 16-byte Spill
	movaps	%xmm11, %xmm9
	mulps	%xmm3, %xmm9
	movaps	%xmm3, %xmm10
	insertps	$16, 20(%rcx), %xmm0
	insertps	$32, 24(%rcx), %xmm0
	subps	%xmm1, %xmm0
	pshufd	$85, %xmm0, %xmm3       # xmm3 = xmm0[1,1,1,1]
	movdqa	%xmm3, (%rsp)           # 16-byte Spill
	movdqa	%xmm3, %xmm1
	movdqa	%xmm3, %xmm14
	mulps	%xmm5, %xmm1
	pshufd	$-86, %xmm0, %xmm8      # xmm8 = xmm0[2,2,2,2]
...



Even LDC2 compiler doesn't get anywhere close to such good/efficient usage of SIMD instructions. And the compiler is also able to spread the work on multiple cores. I think D is meant to be used for similar numerical code too, so perhaps the little amount of ideas contained in this very C-like language is worth stealing and adding to D.

Bye,
bearophile
1 2
Next ›   Last »