x86 / x64 머신 코드에서의 골프 팁


27

나는 그런 질문이 없다는 것을 알았으므로 여기에 있습니다.

기계 코드에서 골프에 대한 일반적인 팁이 있습니까? 팁이 특정 환경이나 전화 규칙에만 적용되는 경우 답을 지정하십시오.

답변 당 하나의 팁만 입력하십시오 ( 여기 참조 ).

답변:


11

mov-즉시 상수가 비싸다

이것은 명백 할 수도 있지만, 여전히 여기에 두겠습니다. 일반적으로 값을 초기화해야 할 때 숫자의 비트 수준 표현에 대해 생각하는 것이 좋습니다.

초기화 eax와 함께 0:

b8 00 00 00 00          mov    $0x0,%eax

(단축되어야 성능뿐만 아니라, 코드 사이즈 까지)

31 c0                   xor    %eax,%eax

초기화 eax와 함께 -1:

b8 ff ff ff ff          mov    $-1,%eax

단축 될 수있다

31 c0                   xor    %eax,%eax
48                      dec    %eax

또는

83 c8 ff                or     $-1,%eax

또는보다 일반적으로 8 비트 부호 확장 값은 push -12(2 바이트) / pop %eax(1 바이트)를 사용 하여 3 바이트로 만들 수 있습니다 . 이것은 추가 REX 접두사가없는 64 비트 레지스터에서도 작동합니다. push/ pop기본 피연산자 크기 = 64

6a f3                   pushq  $0xfffffffffffffff3
5d                      pop    %rbp

또는 레지스터에 알려진 상수가 주어지면 lea 123(%eax), %ecx(3 바이트)를 사용하여 다른 근처 상수를 만들 수 있습니다 . 이것은 제로 레지스터 상수 가 필요한 경우에 편리합니다 . xor-zero (2 바이트) + lea-disp8(3 바이트).

31 c0                   xor    %eax,%eax
8d 48 0c                lea    0xc(%eax),%ecx

또한 참조 효율적으로 일에 CPU 레지스터의 모든 비트를 설정


또한 0 이외의 작은 (8 비트) 값으로 레지스터를 초기화하려면 초기화에 예를 들어 push 200; pop edx-3 바이트를 사용하십시오 .
아나톨리 크

2
BTW를 사용하여 레지스터를 -1로 초기화하려면 다음과 같이 사용하십시오 dec.xor eax, eax; dec eax
anatolyg

@anatolyg : 200은 나쁜 예이며 부호 확장 된 imm8에는 맞지 않습니다. 그러나 그렇습니다. push imm8/ pop reg는 3 바이트이며 x86-64에서 64 비트 상수에 환상적입니다 . 여기서 dec/ inc는 2 바이트입니다. 그리고 push r64/ pop 64(2 바이트)에도 3 바이트를 대체 할 수있다 mov r64, r64(REX 3 바이트). 또한 알려진 값이 주어진 것과 같은 것들에 대해 CPU 레지스터의 모든 비트를 1로 효율적으로 설정 하십시오 (예 : 제로 레지스터 다른 상수 가 필요한 경우 푸시 / 팝 대신 LEA를 사용하십시오lea eax, [rcx-1]eax
Peter Cordes

10

많은 경우에 누산기 기반 명령어 (즉 (R|E)AX, 대상 피연산자로 사용되는 명령어 )는 일반적인 경우 명령어보다 1 바이트 더 짧습니다. StackOverflow 에서이 질문 을 참조하십시오 .


일반적으로 가장 유용한 사람이있다 al, imm8처럼, 특별한 경우 or al, 0x20/ sub al, 'a'/ cmp al, 'z'-'a'/ ja .non_alphabetic인 2 사용하는 대신 3의 각 바이트 al도 허용 문자 데이터 lodsb및 / 또는 stosb. 또는 / / al와 같이 cl = 1 또는 0을 홀수 / 짝수로 만드는 것과 같이 EAX의 하위 바이트에 대해 무언가를 테스트 하는 데 사용하십시오 . 그러나 드물기는하지만 32 비트 즉시 필요합니다 . 내 크로마 키 대답 과 같이lodsdtest al, 1setnz clop eax, imm32
Peter Cordes

8

원하는 곳에 전화를 걸기 위해 전화 규칙을 선택하십시오.

답변의 언어는 asm (실제로 기계 코드)이므로 C-compiled-for-86이 아닌 asm으로 작성된 프로그램의 일부로 취급하십시오. 표준 호출 규칙을 사용하여 C에서 함수를 쉽게 호출 할 필요는 없습니다. 그래도 추가 바이트 비용이 들지 않으면 좋은 보너스입니다.

순수한 asm 프로그램에서는 일부 도우미 함수가 자신과 호출자에게 편리한 호출 규칙을 사용하는 것이 일반적입니다. 이러한 함수는 호출 규칙 (입력 / 출력 / 클러버)을 주석과 함께 문서화합니다.

실제로는 asm 프로그램조차도 (대부분의 다른 소스 파일에서) 대부분의 함수에 대해 일관된 호출 규칙을 사용하는 경향이 있지만 중요한 함수는 특별한 일을 할 수 있습니다. 코드 골프에서는 하나의 단일 함수에서 크랩을 최적화하고 있으므로 분명히 중요합니다.


C 프로그램에서 함수를 테스트하려면 args를 올바른 위치에 배치하고 클로버를 추가 레지스터를 저장 / 복원하고 리턴 값 e/rax이없는 경우 리턴 값을 넣는 랩퍼작성할 수 있습니다 .


합리적인 것의 한계 : 발신자에게 불합리한 부담을주지 않는 것 :

  • ESP / RSP는 통화 보존되어 있어야합니다. 다른 정수 정규식은 공정한 게임입니다. (RBP 및 RBX는 일반적으로 일반적인 규칙에 따라 통화 보존되지만 둘 다 클로버 할 있습니다.)
  • RSP를 제외한 모든 레지스터의 arg는 합리적이지만 호출자에게 동일한 arg를 여러 레지스터에 복사하도록 요청하는 것은 적합하지 않습니다.
  • 콜 / 레트에서 DF ( lods/ stos/ 등의 문자열 방향 플래그 )를 명확하게 (위쪽으로) 요구하는 것은 정상입니다. 콜 / 레트에서 정의되지 않은 상태로 두는 것이 좋습니다. 입력시 지워지거나 설정해야하지만 반환 할 때 수정 상태로 두는 것은 이상합니다.

  • x87에서 FP 값을 반환하는 st0것은 합리적이지만 st3다른 x87 레지스터에서 가비지를 사용하여 반환하는 것은 좋지 않습니다. 호출자는 x87 스택을 정리해야합니다. st0비어 있지 않은 더 높은 스택 레지스터 로 반환하는 것조차도 의심 스럽습니다 (여러 값을 반환하지 않는 한).

  • 함수가 호출 될 것이다 call, 그래서 [rsp]당신의 반환 주소입니다. 당신은 할 수 있습니다 피하기 call/ ret링크 레지스터처럼 사용하는 x86 lea rbx, [ret_addr]/ jmp function과와 수익을 jmp rbx,하지만 "합리적인"아니다. 콜 / 레트만큼 효율적이지 않으므로 실제 코드에서 찾을 수있는 것이 아닙니다.
  • RSP 이상의 무제한 메모리를 클로버하는 것은 합리적이지 않지만 스택에서 함수 인수를 클로버하는 것은 일반적인 호출 규칙에서 허용됩니다. x64 Windows는 반환 주소 위에 32 바이트의 섀도 공간이 필요하지만 x86-64 시스템 V는 RSP 아래에 128 바이트의 빨간색 영역을 제공하므로 둘 중 하나가 합리적입니다. (또는 특히 기능보다는 독립형 프로그램에서 훨씬 더 큰 적색 영역)

경계선 사례 : 함수 args로 처음 2 개의 요소가 주어지면 배열에서 시퀀스를 생성하는 함수를 작성하십시오 . 내가 선택한 배열에 발신자 저장소를 시퀀스의 시작을 그냥 배열에 대한 포인터를 전달합니다. 이것은 확실히 질문의 요구 사항을 굽히고 있습니다. 나는 args를 xmm0위해 포장하는 것을 고려했다 movlps [rdi], xmm0.


FLAGS에서 부울 리턴 (조건 코드)

OS X 시스템 호출이이를 수행합니다 ( CF=0오류 없음). 플래그 레지스터를 부울 리턴 값으로 사용하는 것이 좋지 않은 것으로 간주됩니까? .

하나의 JCC로 확인할 수있는 모든 조건은 특히 합리적이며 문제와 의미 적으로 관련된 조건을 선택할 수있는 경우에 특히 타당합니다. (예를 들어, 비교 함수는 플래그를 설정할 수 있으므로 jne같지 않은 경우 사용됩니다).


좁은 인수 (예 : a char)는 부호가 있거나 32 또는 64 비트로 0으로 확장되어야합니다.

이것은 불합리하지 않습니다. 최신 x86 asm에서는 movzx또는 movsx 부분 레지스터 속도 저하를 피하기 위해 또는 사용하는 것이 일반적입니다. 실제로 clang / LLVM은 이미 x86-64 시스템 V 호출 규칙에 대한 문서화되지 않은 확장에 의존하는 코드를 작성합니다. 32 비트보다 좁은 인수는 부호가 있거나 32 비트로 0 확장됩니다 .

원하는 경우 작성 uint64_t하거나 int64_t프로토 타입 으로 64 비트까지 확장을 문서화하거나 설명 할 수 있습니다 . 예를 들어 loop주소 크기 접두사를 사용하여 32 비트 ECX로 크기를 재정의하지 않는 한 (실제로 주소 크기는 피연산자 크기가 아님) RCx의 전체 64 비트를 사용하는 명령어를 사용할 수 있습니다.

참고 long윈도우 64 비트 ABI, 그리고 만 32 비트 타입 리눅스 X32 ABI ; uint64_t보다 명확하고 형식이 짧습니다 unsigned long long.


기존 통화 규칙 :

  • 다른 답변__fastcall 에서 이미 제안한 Windows 32 비트 : ecx및 정수 args edx.

  • x86-64 System V : 레지스터에 많은 인수를 전달하고 REX 접두사없이 사용할 수있는 많은 콜 클로저 레지스터가 있습니다. 더 중요한 것은 실제로 컴파일러가 쉽게 인라인 memcpy또는 memset 을 허용하도록 선택되었습니다 rep movsb. 처음 6 개의 정수 / 포인터 인수는 RDI, RSI, RDX, RCX, R8, R9로 전달됩니다.

    함수 가 시간 을 실행하는 루프 내에서 lodsd/ 명령어를 사용 하는 경우 ( 명령 과 함께 ) " x86-64 System V 호출 규칙과 같이 C에서 호출 가능"이라고 말할 수 있습니다 . 예 : 크로 마키 .stosdrcxloopint foo(int *rdi, const int *rsi, int dummy, uint64_t len)

  • 32 비트 GCC regparm: EAX , ECX, EDX의 정수 인수 는 EAX (또는 EDX : EAX)로 반환됩니다. 반환 값과 동일한 레지스터에 첫 번째 인수를 사용하면 호출자 예제와 함수 속성이있는 프로토 타입의 경우와 같은 일부 최적화가 가능 합니다 . 물론 AL / EAX는 일부 지침에 특별합니다.

  • Linux x32 ABI는 긴 모드에서 32 비트 포인터를 사용하므로 포인터를 수정할 때 REX 접두어를 저장할 수 있습니다 ( 예 : use-case ). 레지스터에 0으로 확장 된 32 비트 음수를 가지지 않는 한 64 비트 주소 크기를 계속 사용할 수 있습니다 (따라서 부호없는 값이 됨 [rdi + rdx]).

    참고 것을 push rsp/ pop rax2 바이트와 동일합니다 mov rax,rsp여전히 수, 복사 2 바이트 전체 64 비트 레지스터.


도전 과제가 배열을 반환하도록 요청할 때 스택에서 반환하는 것이 합리적이라고 생각하십니까? 나는 그것이 값으로 구조체를 반환 할 때 컴파일러가 할 것이라고 생각합니다.
qwr

@qwr : 아니오, 주류 호출 규칙은 반환 값에 숨겨진 포인터를 전달합니다. (일부 규칙은 레지스터에서 작은 구조체를 전달 / 반환합니다). C / C ++ 후드 아래에서 값으로 구조체를 반환 하고 어셈블리 수준에서 x86에서 객체어떻게 작동합니까? 의 끝을 참조하십시오 . . 구조체 내부의 배열 을 전달 하면 x86-64 SysV의 스택에 배열이 복사 됩니다. AMD64 ABI에 따라 어떤 종류의 C11 데이터 형식이 배열 이지만 Windows x64는 비 const 포인터를 전달합니다.
Peter Cordes

합리적이거나 그렇지 않은 것에 대해 어떻게 생각하십니까? 이 규칙에 따라 x86을 계산합니까? codegolf.meta.stackexchange.com/a/8507/17360
qwr

1
@qwr : x86은 "스택 기반 언어"가 아닙니다. x86은 스택 시스템이 아닌 RAM있는 레지스터 시스템 입니다 . 스택 머신은 x87 레지스터와 같은 역 폴리시 표기법과 같습니다. fld / fld / faddp. x86의 호출 스택은 해당 모델에 맞지 않습니다. 모든 일반 호출 규칙은 RSP를 수정하지 않은 채로 두거나 ret 16; 반환 주소를 표시하지 않고 배열을 누른 다음 push rcx/을 누릅니다 ret. 호출자는 배열 크기를 알고 있거나 스택 외부에 RSP를 저장하여 자체를 찾아야합니다.
Peter Cordes

호출 된 함수를 스택 jmp에서 호출 한 후 명령 주소를 호출합니다. ret 스택에서 주소를 팝업하고 해당 주소로 jmp
RosLuP

7

AL / AX / EAX 및 기타 짧은 형식 및 단일 바이트 명령어에는 특수한 경우 짧은 형식 인코딩을 사용하십시오.

예에서는 기본 피연산자 크기가 32 비트 인 32/64 비트 모드를 가정합니다. 피연산자 크기 접두사는 명령어를 EAX 대신 AX로 변경합니다 (또는 16 비트 모드에서는 역).

  • inc/dec레지스터 (8 비트 이외) : inc eax/ dec ebp. (x86-64 아님 : 0x4xopcode 바이트는 REX 접두어로 용도 변경되었으므로 inc r/m32유일한 인코딩입니다.)

    opcode + ModR / M 피연산자 인코딩을inc bl 사용하여 8 비트 는 2 바이트 입니다. 그래서 사용 증가에 안전하게 있는지. (예 : 상위 바이트가 0이 아닌 경우 ZF 결과가 필요하지 않은 경우)inc r/m8inc ebxbl

  • scasd: e/rdi+=4는 레지스터가 읽을 수있는 메모리를 가리켜 야합니다. FLAGS 결과에 관심이없는 경우에도 유용합니다 (예 : cmp eax,[rdi]/ rdi+=4). 그리고 64 비트 모드에서, scasb1 바이트로 동작 할 수inc rdi lodsb 또는 stosb 유용하지 않은 경우.

  • xchg eax, r32: 이것은 0x90 NOP가 온 곳 xchg eax,eax입니다. 예 : / 대신에 / 의 남용을 포함하여 대부분의 명령어가 단일 바이트 인 경우 8 바이트의 GCD에 대해 / 루프 두 개의 xchg명령어를 사용하여 3 개의 레지스터를 다시 정렬 합니다 .cdqidivinc ecxlooptest ecx,ecxjnz

  • cdq: EAX를 EDX : EAX로 부호 확장합니다. 즉, EAX의 높은 비트를 EDX의 모든 비트에 복사합니다. 음이 아닌 것으로 알려진 0을 만들거나 0 / -1을 얻거나 더하거나 마스크로 가려면. x86 히스토리 레슨 : cltqvs.movslq 및 이와 관련된 AT & T vs. 인텔 니모닉 cdqe.

  • lodsb / d : 클로버 링 플래그 와 같음 mov eax, [rsi]/ rsi += 4없음. (DF가 명확하다고 가정하면 표준 호출 규칙에서 함수 입력에 필요합니다.) 또한 stosb / d, 때로는 scas 및 movs / cmps는 거의 없습니다.

  • push/ pop reg. 예를 들어 64 비트 모드에서 push rsp/ pop rdi는 2 바이트이지만 mov rdi, rspREX 접두사가 필요하며 3 바이트입니다.

xlatb존재하지만 거의 유용하지 않습니다. 큰 조회 테이블은 피해야합니다. 또한 AAA / DAA 또는 기타 압축 BCD 또는 2-ASCII 자리 명령어에 대한 사용을 찾지 못했습니다.

1 바이트 lahf/ sahf거의 유용하지 않습니다. 의 대안으로 / 있지만 일반적으로 유용하지 않습니다.lahfand ah, 1setc ah

특히 CF의 sbb eax,eax경우 플래그에 영향을 미치지 않고 효과적으로 수행 되는salc 0 / -1 또는 문서화되지 않았지만 보편적으로 지원되는 1 바이트 (캐리어에서 AL 설정) 를 얻을 수 sbb al,al있습니다. (x86-64에서 제거됨). 사용자 감사 과제 # 1 : Dennis ♦ 에서 SALC를 사용했습니다 .

1 바이트 cmc/ clc/ stc(플립 ( "complement"), clear 또는 set CF)는 거의 유용하지 않지만 기본 10 ^ 9 덩어리 로 확장 정밀도 추가에 사용하는 것으로 나타cmc 났습니다 . CF를 무조건 설정 / 지우려면 일반적으로 다른 명령의 일부로 발생하도록 준비하십시오 (예 : xor eax,eaxCF 및 EAX 지우기). 다른 조건 플래그에는 DF (문자열 방향) 및 IF (인터럽트)에 해당하는 명령어가 없습니다. 캐리 플래그는 많은 명령에 특별합니다. shifts가 설정하고 adc al, 02 바이트로 AL에 추가 할 수 있으며 앞에서 문서화되지 않은 SALC를 언급했습니다.

std/ cld거의 가치가 없어 보인다 . 특히 32 비트 코드에서, 더 나은 단지 사용에의 dec포인터와에 mov또는 메모리 소스 피연산자 대신 이렇게 DF를 설정하는 ALU 명령에 lodsb/ stosb아래 대신 최대의 이동합니다. 당신이 전혀 아래쪽으로 필요 일반적으로, 당신은 아직도 당신이 하나 이상 필요 했어, 그래서 또 다른 포인터가 올라가고 있습니다 stdcld사용에 전체 기능에 lods/ stos모두. 대신 문자열 지시 사항을 위로 사용하십시오. (표준 호출 규칙은 함수 입력에서 DF = 0을 보장하므로을 사용하지 않고 무료로 가정 할 수 있습니다 cld.)


8086 히스토리 : 이러한 인코딩이 존재하는 이유

원래 8086 년 AX은 매우 특별했다 : 지침 좋아 lodsb/ stosb, cbw, mul/ div다른 사람이 암시 적으로 그것을 사용할 수 있습니다. 그것은 여전히 ​​그렇습니다. 현재 x86은 8086의 opcode를 삭제하지 않았습니다 (적어도 공식적으로 문서화 된 것은 아닙니다). 그러나 나중에 CPU는 새로운 지침을 추가하여 먼저 AX로 복사하거나 교체하지 않고 작업을 수행하는 더 좋고 효율적인 방법을 제공했습니다. (32 비트 모드에서 EAX로)

예를 들어, 8086 이상 추가 좋아 부족 movsx/ movzx넣거나 + 이동 사인 확장, 또는 2, 3 피연산자 imul cx, bx, 1234높은 반 결과를 생성하지 않고 암시 적 피연산자가없는 것이다.

또한 8086의 주요 병목 현상은 명령어 가져 오기이므로 코드 크기 최적화는 당시 성능에 중요했습니다 . 8086 ' 의 ISA 디자이너 (스티븐 모스) AX / AL을위한 특별한 경우, 공간을 코딩 연산 코드를 많이 보냈다 모든 기본적인 즉시-SRC ALU- 지침에 대한 특별한 (E) AX / AL-대상 옵 코드를 포함하여 단지 오피 + 즉시, ModR / M 바이트가 없습니다. 2 바이트 add/sub/and/or/xor/cmp/test/... AL,imm8또는 AX,imm16(32 비트 모드) EAX,imm32.

그러나 특별한 경우는 EAX,imm8없으므로 일반적인 ModR / M 인코딩 add eax,4이 더 짧습니다.

일부 데이터에 대해 작업하려는 경우 AX / AL에서 데이터를 원할 것이므로 레지스터를 AX로 바꾸는 것이 바람직 할 수도 있습니다. 아마도 AX에 레지스터를 복사 하는 것보다 mov.

8086 명령어 인코딩에 관한 모든 것들은 lodsb/wEAX를 즉시 사용할 수있는 모든 특수 케이스 인코딩에서 곱셈 / 나눗셈에도 암시적인 사용에 이르기 까지이 패러다임을 지원합니다 .


멀리 가지 마십시오. 특히 8 비트 대신 32 비트 레지스터로 즉시를 사용해야하는 경우 모든 것을 EAX로 바꾸는 것이 자동으로이기는 것은 아닙니다. 또는 레지스터의 여러 변수에 대한 작업을 한 번에 인터리브해야하는 경우. 또는 레지스터가 2 개인 명령어를 사용하는 경우에는 즉시 사용하지 마십시오.

그러나 항상 명심하십시오 : EAX / AL에서 더 짧은 것을하고 있습니까? AL에 이것을 갖도록 재 배열 할 수 있습니까, 또는 현재 이미 사용하고있는 AL을 AL보다 잘 활용하고 있습니까?

안전 할 때마다 8 비트 및 32 비트 작업을 자유롭게 혼합하여 활용할 수 있습니다 (풀 레지스터 등으로 수행 할 필요가 없음).


cdq많은 경우에 div제로가 필요한 edx경우에 유용합니다 .
qwr

1
@qwr : 그렇습니다. 배당금이 2 ^ 31 미만 (즉, 서명 된 것으로 취급 될 때 음수가 아닌)을 알고 있거나 잠재적으로 큰 값으로 설정 하기 전에 사용하는 경우 서명 cdq되지 않은 채로 남용 div될 수 있습니다 eax. 일반적으로 (외부 코드 골프)는 것 사용 cdq을위한 설정으로 idiv, 그리고 xor edx,edxdiv
피터 코르

5

사용 fastcall규칙

x86 플랫폼에는 많은 호출 규칙이 있습니다. 레지스터에 매개 변수를 전달하는 것을 사용해야합니다. x86_64에서는 처음 몇 개의 매개 변수가 레지스터에 전달되므로 아무런 문제가 없습니다. 32 비트 플랫폼에서 기본 호출 규칙 ( cdecl)은 매개 변수를 스택으로 전달하므로 골프에 적합하지 않습니다. 스택의 매개 변수에 액세스하려면 긴 명령이 필요합니다.

fastcall32 비트 플랫폼에서 사용할 때 일반적으로 2 개의 첫 번째 매개 변수가 ecx및에 전달됩니다 edx. 함수에 3 개의 매개 변수가있는 경우 64 비트 플랫폼에서 구현하는 것을 고려할 수 있습니다.

fastcall컨벤션을 위한 C 함수 프로토 타입 ( 이 예제 답변 에서 발췌 ) :

extern int __fastcall SwapParity(int value);                 // MSVC
extern int __attribute__((fastcall)) SwapParity(int value);  // GNU   

또는 C에서 호출 할 코드를 작성할 필요는 없으며 순수한 asm으로 작성하기 때문에 완전한 사용자 지정 호출 규칙을 사용 하십시오 . FLAGS에서 부울을 반환하는 것이 종종 편리합니다.
Peter Cordes

5

128 더하기 대신 -128 빼기

0100 81C38000      ADD     BX,0080
0104 83EB80        SUB     BX,-80

마찬가지로 128 빼기 대신 -128을 더하십시오.


1
이것은 또한 물론, 다른 방향으로 작동합니다 터닝 관련 최적화 할 또한 컴파일러는이 최적화를 알고 : 대신 하위 128 재미있는 사실 -128를 추가 < 128로를 <= 127위한 즉각적인 피연산자의 크기를 줄이기 위해 cmp, 또는 GCC는 항상 정리 선호 -129와 -128이 아닌 경우에도 크기를 줄이기 위해 비교합니다 .
Peter Cordes

4

3 제로 만들기 mul(다음 inc/ dec얻을 +1 / -1은 물론 제로 등)

세 번째 레지스터에서 0을 곱하여 eax와 edx를 0으로 만들 수 있습니다.

xor   ebx, ebx      ; 2B  ebx = 0
mul   ebx           ; 2B  eax=edx = 0

inc   ebx           ; 1B  ebx=1

EAX, EDX 및 EBX는 모두 4 바이트만으로 0이됩니다. 3 바이트로 EAX와 EDX를 0으로 만들 수 있습니다.

xor eax, eax
cdq

그러나 그 시작점에서 하나 이상의 바이트에서 3 번째 0 레지스터를 얻거나 다른 2 바이트에서 +1 또는 -1 레지스터를 얻을 수 없습니다. 대신 mul 기술을 사용하십시오.

사용 사례 : 피보나치 수를 이진수로 연결 .

LOOP루프가 완료되면 ECX는 0이되고 EDX와 EAX를 0으로 만드는 데 사용될 수 있습니다. 으로 항상 첫 번째 0을 만들 필요는 없습니다 xor.


1
이것은 약간 혼란 스럽다. 당신은 확장 할 수 있습니까?
NoOneIsHere1

@NoOneIsEAX와 EDX를 포함하여 3 개의 레지스터를 0으로 설정하려고합니다.
NieDzejkob

4

CPU 레지스터 및 플래그가 알려진 시작 상태에 있음

CPU가 플랫폼 및 OS를 기반으로 알려져 있고 문서화 된 기본 상태에 있다고 가정 할 수 있습니다.

예를 들면 다음과 같습니다.

도스 http://www.fysnet.net/yourhelp.htm

리눅스 x86 ELF http://asm.sourceforge.net/articles/startup.html


1
코드 골프 규칙에 따르면 코드는 하나 이상의 구현에서 작동해야합니다. Linux는 i386 및 x86-64 System V ABI 문서에서 입력시 "정의되지 않았다"고 말하더라도 새로운 사용자 공간 프로세스를 시작하기 전에 모든 등록 (RSP 제외) 및 스택을 0으로 선택합니다 _start. 그렇습니다 . 함수 대신 프로그램을 작성하는 경우 에는이를 활용하는 것이 좋습니다. 나는 극단적 인 피보나치 에서 그렇게했다 . (동적으로 링크 된 실행 파일에서 실행 ld.so 점프하기 전에에 _start, 그리고 않는 레지스터 휴가 쓰레기를하지만, 정적은 당신의 코드입니다.)
피터 코르

3

1을 더하거나 빼려면 멀티 바이트 추가 및 하위 명령어보다 작은 1 바이트 inc또는 dec명령어를 사용하십시오.


32 비트 모드에는 inc/dec r32opcode로 인코딩 된 레지스터 번호와 함께 1 바이트 가 있습니다. 따라서 inc ebx1 바이트이지만 inc bl2 add bl, 1입니다. 물론 이외 의 레지스터의 경우 여전히 작 습니다 al. 또한 점에 유의 inc/ decCF가 수정되지 않은두고 있지만, 다른 플래그를 업데이트합니다.
Peter Cordes

1
x86에서 +2 및 -2에 대해 2
l4m2

3

lea 수학

이것은 아마도 x86에 대해 가장 먼저 배우는 것 중 하나 일 것입니다. lea2, 3, 4, 5, 8 또는 9을 곱하고 오프셋을 추가하는 데 사용할 수 있습니다.

예를 들어 ebx = 9*eax + 3하나의 명령 으로 계산하려면 (32 비트 모드에서) :

8d 5c c0 03             lea    0x3(%eax,%eax,8),%ebx

여기에 오프셋이 없습니다.

8d 1c c0                lea    (%eax,%eax,8),%ebx

와우! 물론 배열 인덱싱 계산 lea과 같은 수학 ebx = edx + 8*eax + 3계산 에도 사용할 수 있습니다 .


1
어쩌면 언급 할만큼 가치가 lea eax, [rcx + 13]64 비트 모드에 대한 노 여분 접두사 버전입니다. 32 비트 피연산자 크기 (결과) 및 64 비트 주소 크기 (입력)
Peter Cordes

3

루프 및 문자열 명령어는 대체 명령어 시퀀스보다 작습니다. 가장 유용한는 loop <label>두 개의 명령 시퀀스보다 작은 dec ECX하고 jnz <label>그리고 lodsb보다 작고 mov al,[esi]그리고 inc si.


2

mov 적용 가능한 경우 하위 레지스터에 작은 즉시

레지스터의 상위 비트가 0임을 이미 알고 있다면, 더 짧은 명령어를 사용하여 즉시 하위 레지스터로 이동할 수 있습니다.

b8 0a 00 00 00          mov    $0xa,%eax

b0 0a                   mov    $0xa,%al

imm8에 push/ pop를 사용 하여 상위 비트를 0으로 설정

Peter Cordes의 신용. xor/ mov는 4 바이트이지만 push/ pop는 3입니다!

6a 0a                   push   $0xa
58                      pop    %eax

mov al, 0xa전체 정규 표현식으로 0 확장하지 않아도되는 경우 좋습니다. 그러나 그렇게하면 xor / mov는 푸시 imm8 / pop 또는 lea알려진 다른 상수의 4 바이트 대 3입니다 . 이것은 4 바이트의 0 개의 레지스터 3 개와 함께 사용mul 하거나 cdq많은 상수가 필요한 경우 유용 할 수 있습니다 .
Peter Cordes

다른 유스 케이스는의 상수에 대한 것이며 [0x80..0xFF]확장 부호 imm8로 표현할 수 없습니다. 또는 예를 들어 명령 후 상위 바이트 를 이미 알고 있다면 점프하지 않는 유일한 방법 은 언제 만들어 졌는지 입니다. (당신 이 이것을 말한 것 같지만, 당신의 예는을 사용합니다 ). 끝났을 때 다른 것이 레지스터를 0 (또는 무엇이든)으로 되돌려 놓는 한 다른 바이트의 레지스터를 사용할 수도 있습니다. 예를 들어 내 피보나치 프로그램 은 ebx를 유지 하고 bl을 사용합니다. mov cl, 0x10looplooprcx=0xor-1024
Peter Cordes

@PeterCordes 나는 당신의 푸시 / 팝 기술을 추가했습니다
qwr

anatolyg가 이미 주석에서 제안한 상수에 대한 기존 답변으로 들어가야 합니다. 그 답변을 편집하겠습니다. IMO 당신은 더 많은 것을 위해 8 비트 피연산자 크기를 사용하도록 제안하기 위해이 작업을 수행해야합니다 ( xchg eax, r32예 : mov bl, 10/ dec bl/ 제외 ) jnz코드는 높은 바이트의 RBX를 신경 쓰지 않습니다.
Peter Cordes

@PeterCordes 흠. 나는 여전히 8 비트 피연산자를 언제 사용할지 잘 모르겠으므로 그 대답에 무엇을 넣을 지 잘 모르겠습니다.
qwr

2

플래그는 많은 지시 후 설정

많은 산술 명령어 후에 캐리 플래그 (서명되지 않음) 및 오버플로 플래그 (서명 됨)가 자동으로 설정됩니다 ( 추가 정보 ). Sign Flag 및 Zero Flag는 많은 산술 및 논리 연산 후에 설정됩니다. 조건부 분기에 사용할 수 있습니다.

예:

d1 f8                   sar    %eax

ZF는이 명령어에 의해 설정되므로 조건 분기에 사용할 수 있습니다.


언제 패리티 플래그를 사용한 적이 있습니까? 당신은 그것이 결과의 하위 8 비트의 수평 xor라는 것을 알고 있습니까? (상관없이 피연산자 크기의 PF는 하위 8 비트에서만 설정 페이지 참조 ). 짝수 / 홀수; 그 검사 ZF 후 test al,1; 일반적으로 무료로 얻지 못합니다. (또는 and al,1홀수 / 짝수에 따라 정수 0/1을 만들려면)
Peter Cordes

어쨌든,이 답변에 " test/ 를 피하기 위해 다른 명령에 의해 이미 설정된 플래그를 사용하십시오"라고 말하면 cmp, 그것은 매우 기본적인 초보자 x86이지만 여전히 공감할만한 가치가 있습니다.
Peter Cordes

@ PeterCordes Huh, 나는 패리티 플래그를 오해 한 것 같습니다. 나는 여전히 내 다른 대답을 위해 노력하고 있습니다. 답을 편집하겠습니다. 당신이 아마 알 수 있듯이, 나는 초보자이므로 기본 팁이 도움이됩니다.
qwr

2

while 루프 대신 do-while 루프를 사용하십시오.

x86에만 해당되는 것은 아니지만 널리 적용되는 초보자 조립 팁입니다. while 루프가 적어도 한 번 실행된다는 것을 알고 있다면 루프 조건 점검이 끝날 때 루프를 do-while 루프로 다시 쓰면 종종 2 바이트 점프 명령이 저장됩니다. 특별한 경우에는을 사용할 수도 있습니다 loop.


2
관련 : 왜 루프는 항상 이런 식으로 컴파일? do{}while()어셈블리에서 자연 반복되는 관용구가 왜 (특히 효율성을 위해) 있는지 설명합니다 . 또한 2 바이트 jecxz/ jrcxz루프 전 loop은 "제로 실행 필요"사례를 "효율적으로"처리하는 데 매우 효과적입니다 ( loop느린 CPU 가 아닌 경우). jecxz또한 사용 가능한 내부 루프가를 구현하기 위해while(ecx){} 함께 jmp바닥에.
Peter Cordes

@PeterCordes는 매우 잘 작성된 답변입니다. 코드 골프 프로그램에서 루프 중간으로 점프하는 용도를 찾고 싶습니다.
qwr

goto jmp 및 들여 쓰기 사용 ... 루프 따르기
RosLuP

2

편리한 전화 규칙을 사용하십시오.

시스템 V 86 스택을 사용하고 시스템 V - 64 용도 rdi, rsi, rdx, rcx, 등의 입력 매개 변수 및 rax반환 값으로,하지만 자신의 호출 규칙을 사용하여 완벽하게 합리적이다. __fastcallecxedx입력 매개 변수로 사용 하고 다른 컴파일러 / OS는 자체 규칙을 사용합니다 . 편리한 경우 스택 및 모든 레지스터를 입력 / 출력으로 사용하십시오.

예 : 1 바이트 솔루션에 대한 영리한 호출 규칙을 사용하는 반복 바이트 카운터

메타 : 레지스터에 입력 쓰기 , 레지스터출력 쓰기

기타 리소스 : 전화 회의에 대한 Agner Fog의 메모


1
나는 전화 회의를 구성하는 것에 관한 이 질문 에 대한 내 자신의 대답게시하고 합리적이고 비합리적인 것을 게시했습니다 .
Peter Cordes

@PeterCordes와 관련이 없으며 x86에서 인쇄하는 가장 좋은 방법은 무엇입니까? 지금까지 인쇄가 필요한 문제를 피했습니다. DOS는 I / O에 유용한 인터럽트가있는 것처럼 보이지만 32/64 비트 응답 만 작성할 계획입니다. 내가 아는 유일한 방법 int 0x80은 많은 설정이 필요합니다.
qwr

예, int 0x8032 비트 코드 또는 syscall64 비트 코드에서를 호출하는 sys_write것이 유일한 방법입니다. 내가 Extreme Fibonacci에 사용한 것입니다 . 64 비트 코드에서 __NR_write = 1 = STDOUT_FILENO, 당신은 할 수 있습니다 mov eax, edi. 또는 mov al, 432 비트 코드에서 EAX의 상위 바이트가 0 인 경우 . 당신은 또한 수 call printf또는 puts, 내가 생각하고, 대답은 "리눅스 + glibc에 대한 86 ASM"을 작성합니다. PLT 또는 GOT 입력 공간 또는 라이브러리 코드 자체를 계산하지 않는 것이 합리적이라고 생각합니다.
Peter Cordes

1
호출자가 a를 전달하고 char*buf수동 형식으로 문자열을 생성하게하는 경향이 있습니다 . 이와 같은 예 (어색 속도 최적화) ASM FizzBuzz I 레지스터에 문자열 데이터를 얻었고, 그 다음에 그것을 저장 mov문자열이 짧고 고정 길이 때문이었다.
Peter Cordes

1

조건부 이동 CMOVcc및 세트 사용SETcc

이것은 나 자신에게 상기시켜주는 것이지만, 조건부 설정 명령이 존재하고 조건부 이동 명령이 프로세서 P6 (Pentium Pro) 이상에 존재합니다. EFLAGS에 설정된 하나 이상의 플래그를 기반으로하는 많은 명령이 있습니다.


1
분기가 일반적으로 더 작습니다. 자연스럽게 맞지만 cmov2 바이트 opcode ( 0F 4x +ModR/M)가있어 최소 3 바이트 인 경우가 있습니다. 그러나 소스는 r / m32이므로 조건부로 3 바이트로로드 할 수 있습니다. 분기 이외의 다른 setcc경우보다 유용합니다 cmovcc. 여전히 기준선 386 명령어가 아니라 전체 명령어 세트를 고려하십시오. (SSE2 및 BMI / BMI2 명령어는 너무 커서 거의 유용하지 않습니다. rorx eax, ecx, 32mov + ror보다 6 바이트 더 깁니다. POPCNT 또는 PDEP가 많은 것을 저장하지 않는 한 골프는 아닙니다. 성능은 훌륭합니다)
Peter Cordes

@PeterCordes에게 감사드립니다 setcc.
qwr

1

jmpif / then / else 대신 if / then을 정렬하여 바이트 절약

이것은 확실히 매우 기본적입니다. 골프 때 생각할만한 것으로 게시 할 것이라고 생각했습니다. 예를 들어, 16 진수 문자를 디코딩하려면 다음과 같은 간단한 코드를 고려하십시오.

    cmp $'A', %al
    jae .Lletter
    sub $'0', %al
    jmp .Lprocess
.Lletter:
    sub $('A'-10), %al
.Lprocess:
    movzbl %al, %eax
    ...

"then"케이스가 "else"케이스가되도록함으로써 2 바이트 단축 할 수 있습니다.

    cmp $'A', %al
    jb .digit
    sub $('A'-'0'-10), %eax
.digit:
    sub $'0', %eax
    movzbl %al, %eax
    ...

성능을 최적화 할 때, 특히 sub한 경우에 중요한 경로 의 추가 대기 시간이 루프로 전달되는 종속 체인의 일부가 아닌 경우 ( 일반적으로이 작업 은 4 비트 청크를 병합 할 때까지 각 입력 숫자가 독립적 인 경우) ). 그러나 어쨌든 +1을 추측합니다. BTW, 당신의 예는 별도 놓친 최적화를 가지고 : 당신이 필요한 거라면 movzx다음 어쨌든 말 사용 sub $imm, %al의 노 modrm 2 바이트 인코딩을 활용하지 EAX를 op $imm, %al.
Peter Cordes

또한 다음 cmp을 수행 하여 를 제거 할 수 있습니다 sub $'A'-10, %al. jae .was_alpha; add $('A'-10)-'0'. (논리가 맞다고 생각합니다). 참고 'A'-10 > '9'때문에 모호함이 없다. 문자에 대한 수정 값을 빼면 10 진수가 줄 바꿈됩니다. 따라서 입력이 유효한 16 진수라고 가정하면 안전합니다.
Peter Cordes

0

esi를 esp로 설정하고 일련의 lodsd / xchg reg, eax를 수행하여 스택에서 순차 오브젝트를 페치 할 수 있습니다.


왜 이것이 pop eax/ pop edx/ ... 보다 낫 습니까? 스택에 그대로 두어야하는 경우 pushESP를 복원 한 후에도 개체 당 2 바이트 씩 복원 할 수 있습니다 mov esi,esp. 또는 64 바이트 코드에서 pop8 바이트를 얻는 4 바이트 객체를 의미 했 습니까? BTW, 당신도 사용할 수 있습니다 pop보다 더 나은 성능을 버퍼를 통해 루프에 lodsd대한 예를 들어, 익스트림 피보나치 확장 된 정밀도 추가
피터 코르

"lea esi, [esp + ret address의 크기]] 이후에 더 유용합니다. 예비 레지스터가 없으면 pop을 사용할 수 없습니다.
peter ferrie

아, 함수 인수? 아주 드물게 레지스터가있는 것보다 더 많은 인수를 원하거나 호출자가 레지스터에 모두 전달하는 대신 메모리에 하나를 남겨두기를 원할 것입니다. (표준 레지스터 호출 규칙 중 하나가 완벽하게 맞지 않을 경우 사용자 지정 호출 규칙 사용에 대한 반제품 답변이 있습니다.)
Peter Cordes

fastcall 대신 cdecl은 매개 변수를 스택에 남겨두고 많은 매개 변수를 갖는 것은 쉽습니다. 예를 들어 github.com/peterferrie/tinycrypt를 참조하십시오.
peter ferrie

0

codegolf 및 ASM의 경우 : 명령어 사용은 레지스터 만 사용, 팝 팝, 레지스터 메모리 또는 메모리 즉시 최소화


0

64 비트 레지스터를 복사하려면 push rcx; pop rdx3 바이트 대신 mov.
push / pop의 기본 피연산자 크기는 REX 접두사가 필요없는 64 비트입니다.

  51                      push   rcx
  5a                      pop    rdx
                vs.
  48 89 ca                mov    rdx,rcx

(피연산자 크기 접두사는 푸시 / 팝 크기를 16 비트로 대체 할 수 있지만 32 비트 푸시 / 팝 피연산자 크기는 REX.W = 0 인 경우에도 64 비트 모드에서 인코딩 할 수 없습니다 .)

두 레지스터 중 하나 또는 모두가 r8.. r15이면 movpush 및 / 또는 pop에 REX 접두사가 필요하므로 사용하십시오. 최악의 경우 둘 다 REX 접두사가 필요한 경우 실제로 손실됩니다. 어쨌든 코드 골프에서는 r8..r15를 피해야합니다.


NASM 매크로로 개발하는 동안 소스를 더 읽기 쉽게 유지할 수 있습니다 . RSP 아래 8 바이트를 밟는다는 것을 기억하십시오. (x86-64 시스템 V의 적색 영역에서). 그러나 정상적인 조건에서는 64 비트 mov r64,r64또는mov r64, -128..127

    ; mov  %1, %2       ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
%macro MOVE 2
    push  %2
    pop   %1
%endmacro

예 :

   MOVE  rax, rsi            ; 2 bytes  (push + pop)
   MOVE  rbp, rdx            ; 2 bytes  (push + pop)
   mov   ecx, edi            ; 2 bytes.  32-bit operand size doesn't need REX prefixes

   MOVE  r8, r10             ; 4 bytes, don't use
   mov   r8, r10             ; 3 bytes, REX prefix has W=1 and the bits for reg and r/m being high

   xchg  eax, edi            ; 1 byte  (special xchg-with-accumulator opcodes)
   xchg  rax, rdi            ; 2 bytes (REX.W + that)

   xchg  ecx, edx            ; 2 bytes (normal xchg + modrm)
   xchg  rcx, rdx            ; 3 bytes (normal REX + xchg + modrm)

xchg예제 의 일부는 때때로 EAX 또는 RAX에 값을 가져와야하며 이전 사본을 보존하지 않아도되기 때문입니다. 그러나 push / pop은 실제로 교환하는 데 도움이되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.