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아래 대신 최대의 이동합니다. 당신이 전혀 아래쪽으로 필요 일반적으로, 당신은 아직도 당신이 하나 이상 필요 했어, 그래서 또 다른 포인터가 올라가고 있습니다 std및 cld사용에 전체 기능에 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,edx전div
— 피터 코르

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반환 값으로,하지만 자신의 호출 규칙을 사용하여 완벽하게 합리적이다. __fastcall 은 ecx및 edx입력 매개 변수로 사용 하고 다른 컴파일러 / 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.