인라인 어셈블러

인라인 어셈블러 (Inline Assembler)

D는 시스템 프로그래밍 언어로서 인라인 어셈블러를 제공해요. 같은 CPU 제품군에서는 D 구현 간에 인라인 어셈블러가 표준화되어 있어, 예를 들어 Win32 D 컴파일러의 Intel Pentium 인라인 어셈블러는 Intel Pentium에서 도는 Linux의 인라인 어셈블러와 문법 호환이 돼요. 이 문서는 x86과 x86_64 구현을 설명해요.

출처: Inline Assembler

본문

D는 시스템 프로그래밍 언어로서 인라인 어셈블러를 제공해요. 인라인 어셈블러는 같은 CPU 제품군의 D 구현들 사이에서 표준화되어 있어요. 예를 들어 Win32 D 컴파일러의 Intel Pentium 인라인 어셈블러는 Intel Pentium에서 도는 Linux용 인라인 어셈블러와 문법 호환이 돼요.

서로 다른 아키텍처의 D 구현들은 메모리 모델, 함수 호출/반환 규약, 인자 전달 규약 등에서 자유롭게 혁신할 수 있어요.

이 문서는 인라인 어셈블러의 x86 및 x86_64 구현을 설명해요. 컴파일러가 제공하는 인라인 어셈블러 플랫폼 지원은 각각 D_InlineAsm_X86과 D_InlineAsm_X86_64 버전 식별자로 나타나요.

Asm statement

AsmStatement:
    asm FunctionAttributesopt { AsmInstructionListopt }

AsmInstructionList:
    AsmInstruction ;
    AsmInstruction ; AsmInstructionList

어셈블러 명령은 asm 블록 안에 위치해야 해요. 함수처럼 asm 문장은 호출자와 호환되도록 적절한 함수 속성으로 주석을 달아야 해요. asm 문장의 속성은 명시적으로 정의해야 하며, 추론되지 않아요.

@safe는 컴파일러가 어셈블리 문장에 안전성 검사를 하지 않으므로 속성으로 허용되지 않아요 — 대신 @trusted를 사용하세요.

void ok() pure nothrow @safe @nogc
{
    asm pure nothrow @trusted @nogc
    {}
}

void error() @safe @nogc
{
    asm @nogc // Error: asm statement is assumed to be @system - mark it with '@trusted' if it is not
    {}
    asm @safe @nogc // Deprecation: asm statement cannot be @safe, use @trusted instead
    {}
}

Asm instruction

AsmInstruction:
    Identifier : AsmInstruction
    align IntegerExpression
    even
    naked
    db Operands
    ds Operands
    di Operands
    dl Operands
    df Operands
    dd Operands
    de Operands
    db StringLiteral
    ds StringLiteral
    di StringLiteral
    dl StringLiteral
    dw StringLiteral
    dq StringLiteral
    Opcode
    Opcode Operands

Opcode:
    Identifier
    int
    in
    out

Operands:
    Operand
    Operand , Operands

Labels

어셈블러 명령은 다른 문장처럼 레이블을 붙일 수 있어요. goto 문장의 대상이 될 수 있어요. 예를 들어:

void *pc;
asm
{
    call L1          ;
  L1:                ;
    pop  EBX         ;
    mov  pc[EBP],EBX ; // pc now points to code at L1
}

align IntegerExpression

IntegerExpression:
    IntegerLiteral
    Identifier

어셈블러가 다음 어셈블러 명령을 IntegerExpression 경계에 정렬하도록 NOP 명령을 내보내게 해요. IntegerExpression은 컴파일 타임에 2의 거듭제곱인 정수로 평가되어야 해요.

루프 본문의 시작을 정렬하는 것은 실행 속도에 극적인 영향을 줄 때가 있어요.

even

어셈블러가 다음 어셈블러 명령을 짝수 경계에 정렬하도록 NOP 명령을 내보내게 해요.

naked

컴파일러가 함수 프롤로그와 에필로그 시퀀스를 생성하지 않게 해요. 즉, 그 책임이 인라인 어셈블리 프로그래머에게 있다는 뜻이며, 보통 함수 전체를 어셈블러로 작성할 때 사용돼요.

db, ds, di, dl, df, dd, de

이 의사 연산(pseudo op)들은 코드에 원시 데이터를 직접 삽입하기 위한 것이에요. db는 바이트, ds는 16비트 워드, di는 32비트 워드, dl은 64비트 워드, df는 32비트 float, dd는 64비트 double, de는 80비트 확장 real용이에요. 각각 여러 피연산자를 가질 수 있어요. 피연산자가 문자열 리터럴이면, 문자열의 문자 수가 피연산자 수인 것처럼 취급돼요. 각 피연산자당 한 문자가 사용돼요. 예를 들어:

asm
{
    db 5,6,0x83;   // insert bytes 0x05, 0x06, and 0x83 into code
    ds 0x1234;     // insert bytes 0x34, 0x12
    di 0x1234;     // insert bytes 0x34, 0x12, 0x00, 0x00
    dl 0x1234;     // insert bytes 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    df 1.234;      // insert float 1.234
    dd 1.234;      // insert double 1.234
    de 1.234;      // insert real 1.234
    db "abc";      // insert bytes 0x61, 0x62, and 0x63
    ds "abc";      // insert bytes 0x61, 0x00, 0x62, 0x00, 0x63, 0x00
}

Opcodes

지원되는 오퍼코드 목록은 끝에 있어요.

다음 레지스터들이 지원돼요. 레지스터 이름은 항상 대문자예요.

Register:
    AL
    AH
    AX
    EAX

    BL
    BH
    BX
    EBX

    CL
    CH
    CX
    ECX

    DL
    DH
    DX
    EDX

    BP
    EBP

    SP
    ESP

    DI
    EDI

    SI
    ESI

    ES
    CS
    SS
    DS
    GS
    FS

    CR0
    CR2
    CR3
    CR4

    DR0
    DR1
    DR2
    DR3
    DR6
    DR7

    TR3
    TR4
    TR5
    TR6
    TR7

    ST

    ST(0)
    ST(1)
    ST(2)
    ST(3)
    ST(4)
    ST(5)
    ST(6)
    ST(7)

    MM0
    MM1
    MM2
    MM3
    MM4
    MM5
    MM6
    MM7

    XMM0
    XMM1
    XMM2
    XMM3
    XMM4
    XMM5
    XMM6
    XMM7

x86_64는 이 추가 레지스터들을 더해요.

Register64:
    RAX
    RBX
    RCX
    RDX

    BPL
    RBP

    SPL
    RSP

    DIL
    RDI

    SIL
    RSI

    R8B
    R8W
    R8D
    R8

    R9B
    R9W
    R9D
    R9

    R10B
    R10W
    R10D
    R10

    R11B
    R11W
    R11D
    R11

    R12B
    R12W
    R12D
    R12

    R13B
    R13W
    R13D
    R13

    R14B
    R14W
    R14D
    R14

    R15B
    R15W
    R15D
    R15

    XMM8
    XMM9
    XMM10
    XMM11
    XMM12
    XMM13
    XMM14
    XMM15

    YMM0
    YMM1
    YMM2
    YMM3
    YMM4
    YMM5
    YMM6
    YMM7

    YMM8
    YMM9
    YMM10
    YMM11
    YMM12
    YMM13
    YMM14
    YMM15

Special Cases

lock, rep, repe, repne, repnz, repz

이 접두 명령들은 접두하는 명령과 같은 문장에 나타나지 않고, 자신들의 문장에 나타나요. 예를 들어:

asm
{
    rep   ;
    movsb ;
}

pause

이 오퍼코드는 어셈블러가 지원하지 않아요. 대신 사용하세요:

asm
{
    rep  ;
    nop  ;
}

위는 같은 결과를 만들어요.

floating point ops

명령 형식의 두 피연산자 형태를 사용하세요:

fdiv ST(1);     // wrong
fmul ST;        // wrong
fdiv ST,ST(1);  // right
fmul ST,ST(0);  // right

Operands

Operand:
    AsmExp

AsmExp:
    AsmLogOrExp
    AsmLogOrExp ? AsmExp : AsmExp

AsmLogOrExp:
    AsmLogAndExp
    AsmLogOrExp || AsmLogAndExp

AsmLogAndExp:
    AsmOrExp
    AsmLogAndExp && AsmOrExp

AsmOrExp:
    AsmXorExp
    AsmOrExp | AsmXorExp

AsmXorExp:
    AsmAndExp
    AsmXorExp ^ AsmAndExp

AsmAndExp:
    AsmEqualExp
    AsmAndExp & AsmEqualExp

AsmEqualExp:
    AsmRelExp
    AsmEqualExp == AsmRelExp
    AsmEqualExp != AsmRelExp

AsmRelExp:
    AsmShiftExp
    AsmRelExp < AsmShiftExp
    AsmRelExp <= AsmShiftExp
    AsmRelExp > AsmShiftExp
    AsmRelExp >= AsmShiftExp

AsmShiftExp:
    AsmAddExp
    AsmShiftExp << AsmAddExp
    AsmShiftExp >> AsmAddExp
    AsmShiftExp >>> AsmAddExp

AsmAddExp:
    AsmMulExp
    AsmAddExp + AsmMulExp
    AsmAddExp - AsmMulExp

AsmMulExp:
    AsmBrExp
    AsmMulExp * AsmBrExp
    AsmMulExp / AsmBrExp
    AsmMulExp % AsmBrExp

AsmBrExp:
    AsmUnaExp
    AsmBrExp [ AsmExp ]

AsmUnaExp:
    AsmTypePrefix AsmExp
    offsetof AsmExp
    seg AsmExp
    + AsmUnaExp
    - AsmUnaExp
    ! AsmUnaExp
    ~ AsmUnaExp
    AsmPrimaryExp

AsmPrimaryExp:
    IntegerLiteral
    FloatLiteral
    __LOCAL_SIZE
    $
    Register
    Register : AsmExp
    Register64
    Register64 : AsmExp
    DotIdentifier
    this

DotIdentifier:
    Identifier
    Identifier . DotIdentifier
    FundamentalType . Identifier

피연산자 문법은 대체로 Intel CPU 문서 규약을 따르는 편이에요. 특히 두 피연산자 명령에서 소스는 오른쪽 피연산자이고 목적지는 왼쪽 피연산자라는 규약이에요. 이 문법은 D 언어 토크나이저와 호환되고 파싱을 단순화하기 위해 Intel의 문법과는 조금 달라요.

seg는 심볼이 있는 세그먼트 번호를 로드한다는 뜻이에요. 이는 플랫 모델 코드에서는 관련이 없어요. 대신 관련 세그먼트 레지스터에서 이동(move)을 하세요.

점으로 구분된 표현식(dotted expression)은 컴파일 동안 평가되며, 상수를 주거나 대상 레지스터·변수에 맞는 더 높은 수준의 변수를 나타내야 해요.

Operand Types

AsmTypePrefix:
    near ptr
    far ptr
    word ptr
    dword ptr
    qword ptr
    FundamentalType ptr

피연산자 크기가 모호한 경우, 다음과 같이:

add [EAX],3     ;

AsmTypePrefix로 명확히 할 수 있어요:

add  byte ptr [EAX],3 ;
add  int ptr [EAX],7  ;

far ptr은 플랫 모델 코드에서는 관련이 없어요.

Struct/Union/Class Member Offsets

애그리게이트에 대한 포인터가 레지스터에 있을 때 애그리게이트의 멤버에 접근하려면, 멤버의 정규화된 이름의 .offsetof 속성을 사용하세요:

struct Foo { int a,b,c; }
int bar(Foo *f)
{
    asm
    {
        mov EBX,f                   ;
        mov EAX,Foo.b.offsetof[EBX] ;
    }
}
void main()
{
    Foo f = Foo(0, 2, 0);
    assert(bar(&f) == 2);
}

또는 애그리게이트의 스코프 안에서는 멤버 이름만 필요해요:

struct Foo   // or class
{
    int a,b,c;
    int bar()
    {
        asm
        {
            mov EBX, this   ;
            mov EAX, b[EBX] ;
        }
    }
}
void main()
{
    Foo f = Foo(0, 2, 0);
    assert(f.bar() == 2);
}

Stack Variables

스택 변수(함수에 지역이고 스택에 할당된 변수)는 EBP로 인덱스된 변수 이름으로 접근해요:

int foo(int x)
{
    asm
    {
        mov EAX,x[EBP] ; // loads value of parameter x into EAX
        mov EAX,x      ; // does the same thing
    }
}

[EBP]를 생략하면 지역 변수에는 가정돼요. naked를 사용하면 이는 더는 성립하지 않아요.

Special Symbols

$

다음 명령의 시작의 프로그램 카운터를 나타내요. 그래서:

jmp  $  ;

는 jmp 명령 다음에 오는 명령으로 분기해요. $는 jmp 또는 call 명령의 대상으로만 나타날 수 있어요.

__LOCAL_SIZE

이것은 지역 스택 프레임의 지역 바이트 수로 대체돼요. naked가 호출되고 사용자 정의 스택 프레임을 프로그래밍할 때 가장 유용해요.

Opcodes Supported

aaa aad aam aas adc
add addpd addps addsd addss
and andnpd andnps andpd andps
arpl bound bsf bsr bswap
bt btc btr bts call
cbw cdq clc cld clflush
cli clts cmc cmova cmovae
cmovb cmovbe cmovc cmove cmovg
cmovge cmovl cmovle cmovna cmovnae
cmovnb cmovnbe cmovnc cmovne cmovng
cmovnge cmovnl cmovnle cmovno cmovnp
cmovns cmovnz cmovo cmovp cmovpe
cmovpo cmovs cmovz cmp cmppd
cmpps cmps cmpsb cmpsd cmpss
cmpsw cmpxchg cmpxchg8b cmpxchg16b
comisd comiss
cpuid cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi
cvtpd2ps cvtpi2pd cvtpi2ps cvtps2dq cvtps2pd
cvtps2pi cvtsd2si cvtsd2ss cvtsi2sd cvtsi2ss
cvtss2sd cvtss2si cvttpd2dq cvttpd2pi cvttps2dq
cvttps2pi cvttsd2si cvttss2si cwd cwde
da daa das db dd
de dec df di div
divpd divps divsd divss dl
dq ds dt dw emms
enter f2xm1 fabs fadd faddp
fbld fbstp fchs fclex fcmovb
fcmovbe fcmove fcmovnb fcmovnbe fcmovne
fcmovnu fcmovu fcom fcomi fcomip
fcomp fcompp fcos fdecstp fdisi
fdiv fdivp fdivr fdivrp feni
ffree fiadd ficom ficomp fidiv
fidivr fild fimul fincstp finit
fist fistp fisub fisubr fld
fld1 fldcw fldenv fldl2e fldl2t
fldlg2 fldln2 fldpi fldz fmul
fmulp fnclex fndisi fneni fninit
fnop fnsave fnstcw fnstenv fnstsw
fpatan fprem fprem1 fptan frndint
frstor fsave fscale fsetpm fsin
fsincos fsqrt fst fstcw fstenv
fstp fstsw fsub fsubp fsubr
fsubrp ftst fucom fucomi fucomip
fucomp fucompp fwait fxam fxch
fxrstor fxsave fxtract fyl2x fyl2xp1
hlt idiv imul in inc
ins insb insd insw int
into invd invlpg iret iretd
iretq ja jae jb jbe
jc jcxz je jecxz jg
jge jl jle jmp jna
jnae jnb jnbe jnc jne
jng jnge jnl jnle jno
jnp jns jnz jo jp
jpe jpo js jz lahf
lar ldmxcsr lds lea leave
les lfence lfs lgdt lgs
lidt lldt lmsw lock lods
lodsb lodsd lodsw loop loope
loopne loopnz loopz lsl lss
ltr maskmovdqu maskmovq maxpd maxps
maxsd maxss mfence minpd minps
minsd minss mov movapd movaps
movd movdq2q movdqa movdqu movhlps
movhpd movhps movlhps movlpd movlps
movmskpd movmskps movntdq movnti movntpd
movntps movntq movq movq2dq movs
movsb movsd movss movsw movsx
movupd movups movzx mul mulpd
mulps mulsd mulss neg nop
not or orpd orps out
outs outsb outsd outsw packssdw
packsswb packuswb paddb paddd paddq
paddsb paddsw paddusb paddusw paddw
pand pandn pavgb pavgw pcmpeqb
pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw
pextrw pinsrw pmaddwd pmaxsw pmaxub
pminsw pminub pmovmskb pmulhuw pmulhw
pmullw pmuludq pop popa popad
popf popfd por prefetchnta prefetcht0
prefetcht1 prefetcht2 psadbw pshufd pshufhw
pshuflw pshufw pslld pslldq psllq
psllw psrad psraw psrld psrldq
psrlq psrlw psubb psubd psubq
psubsb psubsw psubusb psubusw psubw
punpckhbw punpckhdq punpckhqdq punpckhwd punpcklbw
punpckldq punpcklqdq punpcklwd push pusha
pushad pushf pushfd pxor rcl
rcpps rcpss rcr rdmsr rdpmc
rdtsc rep repe repne repnz
repz ret retf rol ror
rsm rsqrtps rsqrtss sahf sal
sar sbb scas scasb scasd
scasw seta setae setb setbe
setc sete setg setge setl
setle setna setnae setnb setnbe
setnc setne setng setnge setnl
setnle setno setnp setns setnz
seto setp setpe setpo sets
setz sfence sgdt shl shld
shr shrd shufpd shufps sidt
sldt smsw sqrtpd sqrtps sqrtsd
sqrtss stc std sti stmxcsr
stos stosb stosd stosw str
sub subpd subps subsd subss
syscall sysenter sysexit sysret test
ucomisd ucomiss ud2 unpckhpd unpckhps
unpcklpd unpcklps verr verw wait
wbinvd wrmsr xadd xchg xlat
xlatb xor xorpd xorps

Pentium 4 (Prescott) Opcodes Supported

addsubpd addsubps fisttp haddpd haddps
hsubpd hsubps lddqu monitor movddup
movshdup movsldup mwait

AMD Opcodes Supported

pavgusb pf2id pfacc pfadd pfcmpeq
pfcmpge pfcmpgt pfmax pfmin pfmul
pfnacc pfpnacc pfrcp pfrcpit1 pfrcpit2
pfrsqit1 pfrsqrt pfsub pfsubr pi2fd
pmulhrw pswapd

SIMD

SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2와 AVX가 지원돼요.

GCC syntax

GNU D Compiler는 인라인 어셈블러에 GCC 기반의 대안적 문법을 사용해요:

GccAsmStatement:
    asm FunctionAttributesopt { GccAsmInstructionList }

GccAsmInstructionList:
    GccAsmInstruction ;
    GccAsmInstruction ; GccAsmInstructionList

GccAsmInstruction:
    GccBasicAsmInstruction
    GccExtAsmInstruction
    GccGotoAsmInstruction

GccBasicAsmInstruction:
    GccAsmStringExpression

GccExtAsmInstruction:
    GccAsmStringExpression : GccAsmOperandsopt
    GccAsmStringExpression : GccAsmOperandsopt : GccAsmOperandsopt
    GccAsmStringExpression : GccAsmOperandsopt : GccAsmOperandsopt : GccAsmClobbersopt

GccGotoAsmInstruction:
    GccAsmStringExpression : : GccAsmOperandsopt : GccAsmClobbersopt : GccAsmGotoLabelsopt

GccAsmStringExpression:
    StringLiteral
    ( ConditionalExpression )

GccAsmOperands:
    GccSymbolicNameopt GccAsmStringExpression ( AssignExpression )
    GccSymbolicNameopt GccAsmStringExpression ( AssignExpression ) , GccAsmOperands

GccSymbolicName:
    [ Identifier ]

GccAsmClobbers:
    GccAsmStringExpression
    GccAsmStringExpression , GccAsmClobbers

GccAsmGotoLabels:
    Identifier
    Identifier , GccAsmGotoLabels

더 알아보기

  • 인라인 어셈블러를 지원하는 CPU 아키텍처는 D 컴파일러의 D_InlineAsm_X86 / D_InlineAsm_X86_64 버전 식별자로 확인할 수 있어요.
  • GNU D 컴파일러의 문법은 GDC 프로젝트 문서를 참고하세요.
  • dlang.org의 원문에서 최신 내용을 확인할 수 있어요.