Vector support of LLVM

by syoyo

LLVM は SSE と AltiVec によるベクトル処理をサポートしていることがわかりました。


/* vec.c */
#include 

void
mudah()
{
        __m128 a, b, c;

        a = _mm_add_ps(b, c);
}

int
main(int argc, char **argv)
{
        mudah();
}

こんなコードを、llvm-gccでコンパイルして(x86 darwin 上)まずはバイトコードを生成します。


$ llvm-gcc -c -emit-llvm -msse2 vec.c
$ llvm-dis vec.o
$ cat vec.o.ll

; ModuleID = 'vec.o'
target datalayout = "e-p:32:32"
target endian = little
target pointersize = 32
target triple = "i686-apple-darwin8"

implementation   ; Functions:

void %func() {
entry:
        %tmp = alloca , align 16
        %a = alloca , align 16
        %b = alloca , align 16
        %c = alloca , align 16
        %__B = alloca , align 16
        %__A = alloca , align 16
        %tmp2 = alloca , align 16
        "alloca point" = cast int 0 to int
        %tmp = load * %b
        store  %tmp, * %__A
        %tmp1 = load * %c
        store  %tmp1, * %__B
        %tmp3 = load * %__B
        %tmp4 = load * %__A
        %tmp5 = add  %tmp4, %tmp3
        store  %tmp5, * %tmp2
        %tmp6 = load * %tmp2
        store  %tmp6, * %tmp
        br label %bb

bb:             ; preds = %entry
        %tmp7 = load * %tmp
        store  %tmp7, * %a
        br label %return

return:         ; preds = %bb
        ret void
}

int %main(int %argc, sbyte** %argv) {
entry:
        %argc_addr = alloca int
        %argv_addr = alloca sbyte**
        %retval = alloca int, align 4
        "alloca point" = cast int 0 to int
        store int %argc, int* %argc_addr
        store sbyte** %argv, sbyte*** %argv_addr
        call void (...)* cast (void ()* %func to void (...)*)( )
        br label %return

return:         ; preds = %entry
        %retval = load int* %retval
        ret int %retval
}

add インストラクションが、float 4 個の型の変数に対して行われています。
バイトコードから、Core2 プロセッサをターゲットとしてアセンブラに変換してみます。


$ llc -mcpu=core2 vec.o
$ cat vec.o.s

.text
        .align  4
        .globl  _func
_func:
        subl $124, %esp
        movaps 64(%esp), %xmm0
        movaps %xmm0, 16(%esp)
        movaps 48(%esp), %xmm0
        movaps %xmm0, 32(%esp)
        addps 16(%esp), %xmm0
        movaps %xmm0, (%esp)
        movaps %xmm0, 96(%esp)
LBB1_1: #bb
        movaps 96(%esp), %xmm0
        movaps %xmm0, 80(%esp)
LBB1_2: #return
        addl $124, %esp
        ret


        .align  4
        .globl  _main
_main:
        subl $28, %esp
        fnstcw 14(%esp)
        movb $2, 15(%esp)
        fldcw 14(%esp)
        movl 32(%esp), %eax
        movl %eax, 24(%esp)
        movl 36(%esp), %eax
        movl %eax, 20(%esp)
        call _func
LBB2_1: #return
        movl 16(%esp), %eax
        addl $28, %esp
        ret

        .subsections_via_symbols

addps 命令など、SSE2 命令が使われています。期待どおりです。

では、SSE2 命令がない CPU では?


$ llc -f -mcpu=i386 vec.o
$ cat vec.o.s

.text
        .align  4
        .globl  _func
_func:
        subl $124, %esp
        flds 76(%esp)
        flds 68(%esp)
        flds 72(%esp)
        flds 64(%esp)
        fxch %st(3)
        fstps 28(%esp)
        fxch %st(1)
        fstps 20(%esp)
        fstps 24(%esp)
        fstps 16(%esp)
        flds 60(%esp)
        flds 52(%esp)
        flds 56(%esp)
        flds 48(%esp)
        fxch %st(3)
        fstps 44(%esp)
        fxch %st(1)
        fstps 36(%esp)
        fstps 40(%esp)
        fstps 32(%esp)
        flds 20(%esp)
        flds 28(%esp)
        flds 24(%esp)
        flds 16(%esp)
        fxch %st(3)
        fadds 36(%esp)
        fxch %st(2)
        fadds 44(%esp)
        fxch %st(1)
        fadds 40(%esp)
        fxch %st(3)
        fadds 32(%esp)
        fxch %st(1)
        fstps 12(%esp)
        fxch %st(1)
        fstps 4(%esp)
        fxch %st(1)
        fstps 8(%esp)
        fstps (%esp)
        flds 12(%esp)
        flds 4(%esp)
        flds 8(%esp)
        flds (%esp)
        fxch %st(3)
        fstps 108(%esp)
        fxch %st(1)
        fstps 100(%esp)
        fstps 104(%esp)
        fstps 96(%esp)
        #FP_REG_KILL
LBB1_1: #bb
        flds 108(%esp)
        flds 100(%esp)
        flds 104(%esp)
        flds 96(%esp)
        fxch %st(3)
        fstps 92(%esp)
        fxch %st(1)
        fstps 84(%esp)
        fstps 88(%esp)
        fstps 80(%esp)
        #FP_REG_KILL
LBB1_2: #return
        addl $124, %esp
        ret


        .align  4
        .globl  _main
_main:
        subl $28, %esp
        fnstcw 14(%esp)
        movb $2, 15(%esp)
        fldcw 14(%esp)
        movl 32(%esp), %eax
        movl %eax, 24(%esp)
        movl 36(%esp), %eax
        movl %eax, 20(%esp)
        call _func
LBB2_1: #return
        movl 16(%esp), %eax
        addl $28, %esp
        ret

        .subsections_via_symbols

めでたく? SSE2 命令を使わずに X87 命令を使うようなアセンブラが出力されました。

さらに、ターゲットを AltiVec 対応の PPC にすると、


$ llc -f -march=ppc32 -mcpu=g4 vec.o
$ cat vec.o.s

.text
        .globl  _mudah
        .align  4
_mudah:
        mfspr r2, 256
        oris r3, r2, 12288
        mtspr 256, r3
        addi r3, r1, -48
        lvx v2, 0, r3
        addi r3, r1, -96
        stvx v2, 0, r3
        addi r4, r1, -64
        lvx v2, 0, r4
        addi r4, r1, -80
        stvx v2, 0, r4
        lvx v3, 0, r3
        addi r3, r1, -112
        vaddfp v2, v3, v2
        addi r4, r1, -16
        stvx v2, 0, r3
        stvx v2, 0, r4
LBB1_1: ;bb
        addi r3, r1, -16
        lvx v2, 0, r3
        addi r3, r1, -32
        stvx v2, 0, r3
LBB1_2: ;return
        mtspr 256, r2
        blr


        .globl  _main
        .align  4
_main:
        stwu r1, -80(r1)
        stw r1, 76(r1)
        mflr r11
        stw r11, 88(r1)
        stw r3, 72(r1)
        stw r4, 68(r1)
        bl _mudah
LBB2_1: ;return
        lwz r3, 64(r1)
        lwz r11, 88(r1)
        mtlr r11
        lwz r1, 76(r1)
        addi r1, r1, 80
        blr

        .subsections_via_symbols

AltiVec 命令(vaddfp)が使われるようになっています。
(アセンブラ自体は効率悪そうだけど…)

これはつまり、、、

o SSE なプログラムを書いておけば、バイトコードから SSE コードとスカラコード、AltiVec コードを出力できる

ということになりますね。パフォーマンスはともかくとして、SSE と AltiVec、SIMD とスカラというふうに別々にプログラミングしなくてよいのはいいかも(もう最初っから SSE SIMD でプログラミング)。

Advertisements