C++とアセンブラー⑤

C++アセンブラー④で紹介したように、xmmレジスターを保護しただけでは、ymmレジスターやzmmレジスターの保護が保証されないことが分かった。そこで、ここでは512ビットのSIMDをサポートしたCPUはzmmレジスターを、256ビットのSIMDをサポートしたCPUはymmレジスターを保護する例を示す。併せて、mov命令へ適切なアライメントを保証できるとは限らないので、アライメントを要求しない命令に書き換える。

ymmレジスターの保護

256ビットのSIMDをサポートしたCPUはymmレジスターを搭載している。このような環境でymmレジスターを保護する例を示す。呼び出し側のC++ソースコードを示す。

#include <iostream>

using namespace std;

extern "C" void myCpuid(int*, int); // assembler function

int main()
{
    int regs[4];
    char CPUString[(sizeof(int)) * 3 + 1]{};

    try
    {
        myCpuid(regs, 0);
        *((int*)(CPUString + 0)) = regs[1];
        *((int*)(CPUString + 4)) = regs[3];
        *((int*)(CPUString + 8)) = regs[2];
        CPUString[sizeof CPUString - 1] = '\0';

        cout << "Vender Id: " << CPUString << endl;
    }
    catch (...)  // その他の例外をキャッチ
    {
        cerr << "unknown exeption" << endl;
    }
    return 0;
}

プロローグ、エピローグ、そして構造体の定義などをincファイルへ分離したので、それを示す。

; prolog
PROLOG  macro   stack
        alloc_stack(size stack)                 ; prolog
        vmovups stack.ymm6sv[rsp],  ymm6
        vmovups stack.ymm7sv[rsp],  ymm7
        vmovups stack.ymm8sv[rsp],  ymm8
        vmovups stack.ymm9sv[rsp],  ymm9
        vmovups stack.ymm10sv[rsp], ymm10
        vmovups stack.ymm11sv[rsp], ymm11
        vmovups stack.ymm12sv[rsp], ymm12
        vmovups stack.ymm13sv[rsp], ymm13
        vmovups stack.ymm14sv[rsp], ymm14
        vmovups stack.ymm15sv[rsp], ymm15

        mov     stack.rbxsv[rsp], rbx
        mov     stack.rsisv[rsp], rsi
        mov     stack.rdisv[rsp], rdi
        mov     stack.rbpsv[rsp], rbp
        mov     stack.r12sv[rsp], r12
        mov     stack.r13sv[rsp], r13
        mov     stack.r14sv[rsp], r14
        mov     stack.r15sv[rsp], r15
        .endprolog                              ; end of prolog
        endm

; epilog
EPILOG  macro   stack
        vmovups ymm6,  stack.ymm6sv[rsp]        ; epilog, restore
        vmovups ymm7,  stack.ymm7sv[rsp]
        vmovups ymm8,  stack.ymm8sv[rsp]
        vmovups ymm9,  stack.ymm9sv[rsp]
        vmovups ymm10, stack.ymm10sv[rsp]
        vmovups ymm11, stack.ymm11sv[rsp]
        vmovups ymm12, stack.ymm12sv[rsp]
        vmovups ymm13, stack.ymm13sv[rsp]
        vmovups ymm14, stack.ymm14sv[rsp]
        vmovups ymm15, stack.ymm15sv[rsp]

        mov     rbx,   stack.rbxsv[rsp]
        mov     rsi,   stack.rsisv[rsp]
        mov     rdi,   stack.rdisv[rsp]
        mov     rbp,   stack.rbpsv[rsp]
        mov     r12,   stack.r12sv[rsp]
        mov     r13,   stack.r13sv[rsp]
        mov     r14,   stack.r14sv[rsp]
        mov     r15,   stack.r15sv[rsp]
        add     rsp,   size stack               ; end of epilog
        ret
        endm

; data structure to push on the stack
stkF    struct
        ymm6sv  ymmword ?
        ymm7sv  ymmword ?
        ymm8sv  ymmword ?
        ymm9sv  ymmword ?
        ymm10sv ymmword ?
        ymm11sv ymmword ?
        ymm12sv ymmword ?
        ymm13sv ymmword ?
        ymm14sv ymmword ?
        ymm15sv ymmword ?

        rbxsv   dq      ?
        rsisv   dq      ?
        rdisv   dq      ?
        rbpsv   dq      ?
        r12sv   dq      ?
        r13sv   dq      ?
        r14sv   dq      ?
        r15sv   dq      ?
                dq      ?                       ; padding
stkF    ends
; error, if size is not a 16 multiples and plus 8
.erre (size stkF mod 16) eq 8, <the size of stack structure must be 16n+8>

次に、アセンブリ言語で記述したソースコードを示す。

include ksamd64.inc
include reg256.inc

_TEXT       segment

;
; rcx = stringの先頭アドレス。
; rdx = function #
;
        public  myCpuid
        align   16

myCpuid proc    frame
        PROLOG  stkF                ; prolog

        mov     r9, rcx             ; save param addr

        mov     eax, edx
        cpuid
        mov     [r9+0],  eax
        mov     [r9+4],  ebx
        mov     [r9+8],  ecx
        mov     [r9+12], edx

        EPILOG  stkF                ; epilog
myCpuid endp

_TEXT   ends
        end

実行結果:Vender Id: GenuineIntel

zmmレジスターの保護

512ビットのSIMDをサポートしたCPUはzmmレジスターを搭載している。このような環境でzmmレジスターを保護する例を示す。呼び出し側のC++ソースコードを示す。呼び出し側のC++ソースコードは、「ymmレジスターの保護」と同じなので、incファイルとアセンブリ言語で記述したソースコードを示す。

; prolog
PROLOG  macro   stack
        alloc_stack(size stack)             ; prolog
        vmovups stack.zmm6sv[rsp],  zmm6
        vmovups stack.zmm7sv[rsp],  zmm7
        vmovups stack.zmm8sv[rsp],  zmm8
        vmovups stack.zmm9sv[rsp],  zmm9
        vmovups stack.zmm10sv[rsp], zmm10
        vmovups stack.zmm11sv[rsp], zmm11
        vmovups stack.zmm12sv[rsp], zmm12
        vmovups stack.zmm13sv[rsp], zmm13
        vmovups stack.zmm14sv[rsp], zmm14
        vmovups stack.zmm15sv[rsp], zmm15
;       vmovups stack.zmm16sv[rsp], zmm16
;       vmovups stack.zmm17sv[rsp], zmm17
;       vmovups stack.zmm18sv[rsp], zmm18
;       vmovups stack.zmm19sv[rsp], zmm19
;       vmovups stack.zmm20sv[rsp], zmm20
;       vmovups stack.zmm21sv[rsp], zmm21
;       vmovups stack.zmm22sv[rsp], zmm22
;       vmovups stack.zmm23sv[rsp], zmm23
;       vmovups stack.zmm24sv[rsp], zmm24
;       vmovups stack.zmm25sv[rsp], zmm25
;       vmovups stack.zmm26sv[rsp], zmm26
;       vmovups stack.zmm27sv[rsp], zmm27
;       vmovups stack.zmm28sv[rsp], zmm28
;       vmovups stack.zmm29sv[rsp], zmm29
;       vmovups stack.zmm30sv[rsp], zmm30
;       vmovups stack.zmm31sv[rsp], zmm31

        mov     stack.rbxsv[rsp], rbx
        mov     stack.rsisv[rsp], rsi
        mov     stack.rdisv[rsp], rdi
        mov     stack.rbpsv[rsp], rbp
        mov     stack.r12sv[rsp], r12
        mov     stack.r13sv[rsp], r13
        mov     stack.r14sv[rsp], r14
        mov     stack.r15sv[rsp], r15
        .endprolog                          ; end of prolog
        endm

; epilog
EPILOG  macro   stack
        vmovups zmm6,  stack.zmm6sv[rsp]    ; epilog, restore
        vmovups zmm7,  stack.zmm7sv[rsp]
        vmovups zmm8,  stack.zmm8sv[rsp]
        vmovups zmm9,  stack.zmm9sv[rsp]
        vmovups zmm10, stack.zmm10sv[rsp]
        vmovups zmm11, stack.zmm11sv[rsp]
        vmovups zmm12, stack.zmm12sv[rsp]
        vmovups zmm13, stack.zmm13sv[rsp]
        vmovups zmm14, stack.zmm14sv[rsp]
        vmovups zmm15, stack.zmm15sv[rsp]
;       vmovups zmm16, stack.zmm16sv[rsp]
;       vmovups zmm17, stack.zmm17sv[rsp]
;       vmovups zmm18, stack.zmm18sv[rsp]
;       vmovups zmm19, stack.zmm19sv[rsp]
;       vmovups zmm20, stack.zmm20sv[rsp]
;       vmovups zmm21, stack.zmm21sv[rsp]
;       vmovups zmm22, stack.zmm22sv[rsp]
;       vmovups zmm23, stack.zmm23sv[rsp]
;       vmovups zmm24, stack.zmm24sv[rsp]
;       vmovups zmm25, stack.zmm25sv[rsp]
;       vmovups zmm26, stack.zmm26sv[rsp]
;       vmovups zmm27, stack.zmm27sv[rsp]
;       vmovups zmm28, stack.zmm28sv[rsp]
;       vmovups zmm29, stack.zmm29sv[rsp]
;       vmovups zmm30, stack.zmm30sv[rsp]
;       vmovups zmm31, stack.zmm31sv[rsp]

        mov     rbx,   stack.rbxsv[rsp]
        mov     rsi,   stack.rsisv[rsp]
        mov     rdi,   stack.rdisv[rsp]
        mov     rbp,   stack.rbpsv[rsp]
        mov     r12,   stack.r12sv[rsp]
        mov     r13,   stack.r13sv[rsp]
        mov     r14,   stack.r14sv[rsp]
        mov     r15,   stack.r15sv[rsp]
        add     rsp,   size stack           ; end of epilog
        ret
        endm

; data structure to push on the stack
stkF    struct
        zmm6sv  zmmword ?
        zmm7sv  zmmword ?
        zmm8sv  zmmword ?
        zmm9sv  zmmword ?
        zmm10sv zmmword ?
        zmm11sv zmmword ?
        zmm12sv zmmword ?
        zmm13sv zmmword ?
        zmm14sv zmmword ?
        zmm15sv zmmword ?
;       zmm16sv zmmword ?
;       zmm17sv zmmword ?
;       zmm18sv zmmword ?
;       zmm19sv zmmword ?
;       zmm20sv zmmword ?
;       zmm21sv zmmword ?
;       zmm22sv zmmword ?
;       zmm23sv zmmword ?
;       zmm24sv zmmword ?
;       zmm25sv zmmword ?
;       zmm26sv zmmword ?
;       zmm27sv zmmword ?
;       zmm28sv zmmword ?
;       zmm29sv zmmword ?
;       zmm30sv zmmword ?
;       zmm31sv zmmword ?

        rbxsv   dq      ?
        rsisv   dq      ?
        rdisv   dq      ?
        rbpsv   dq      ?
        r12sv   dq      ?
        r13sv   dq      ?
        r14sv   dq      ?
        r15sv   dq      ?
                dq      ?                   ; padding
stkF    ends
; error, if size is not a 16 multiples and plus 8
.erre (size stkF mod 16) eq 8, <the size of stack structure must be 16n+8>

アセンブリ言語で記述したソースコード

include ksamd64.inc
include reg512.inc

_TEXT       segment

;
; rcx = stringの先頭アドレス。
; rdx = function #
;
        public  myCpuid
        align   16

myCpuid proc    frame
        PROLOG  stkF                ; prolog

        mov     r9, rcx             ; save param addr

        mov     eax, edx
        cpuid
        mov     [r9+0],  eax
        mov     [r9+4],  ebx
        mov     [r9+8],  ecx
        mov     [r9+12], edx

        EPILOG  stkF                ; epilog
myCpuid endp

_TEXT   ends
        end

解説

ymmやzmmレジスターの移動にvmovapsではなくvmovupsを使用する。vmovapsはメモリーアドレスに一定のアライメントを要求する。この条件を満たすとは限らないため、アライメントを求めないvmovupsを使用した。もし、アライメントを満足できるときはvmovapsを使ったほうが高速なので、vmovapsを推奨する。なお、zmmレジスターは32本あるが半分しか保護していない。これはプロローグのサイズ制限があるためである。