답변:
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
dec
.xor eax, eax; dec eax
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
많은 경우에 누산기 기반 명령어 (즉 (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 비트 즉시 필요합니다 . 내 크로마 키 대답 과 같이lodsd
test al, 1
setnz cl
op eax, imm32
답변의 언어는 asm (실제로 기계 코드)이므로 C-compiled-for-86이 아닌 asm으로 작성된 프로그램의 일부로 취급하십시오. 표준 호출 규칙을 사용하여 C에서 함수를 쉽게 호출 할 필요는 없습니다. 그래도 추가 바이트 비용이 들지 않으면 좋은 보너스입니다.
순수한 asm 프로그램에서는 일부 도우미 함수가 자신과 호출자에게 편리한 호출 규칙을 사용하는 것이 일반적입니다. 이러한 함수는 호출 규칙 (입력 / 출력 / 클러버)을 주석과 함께 문서화합니다.
실제로는 asm 프로그램조차도 (대부분의 다른 소스 파일에서) 대부분의 함수에 대해 일관된 호출 규칙을 사용하는 경향이 있지만 중요한 함수는 특별한 일을 할 수 있습니다. 코드 골프에서는 하나의 단일 함수에서 크랩을 최적화하고 있으므로 분명히 중요합니다.
C 프로그램에서 함수를 테스트하려면 args를 올바른 위치에 배치하고 클로버를 추가 레지스터를 저장 / 복원하고 리턴 값 e/rax
이없는 경우 리턴 값을 넣는 랩퍼 를 작성할 수 있습니다 .
콜 / 레트에서 DF ( lods
/ stos
/ 등의 문자열 방향 플래그 )를 명확하게 (위쪽으로) 요구하는 것은 정상입니다. 콜 / 레트에서 정의되지 않은 상태로 두는 것이 좋습니다. 입력시 지워지거나 설정해야하지만 반환 할 때 수정 상태로 두는 것은 이상합니다.
x87에서 FP 값을 반환하는 st0
것은 합리적이지만 st3
다른 x87 레지스터에서 가비지를 사용하여 반환하는 것은 좋지 않습니다. 호출자는 x87 스택을 정리해야합니다. st0
비어 있지 않은 더 높은 스택 레지스터 로 반환하는 것조차도 의심 스럽습니다 (여러 값을 반환하지 않는 한).
call
, 그래서 [rsp]
당신의 반환 주소입니다. 당신은 할 수 있습니다 피하기 call
/ ret
링크 레지스터처럼 사용하는 x86 lea rbx, [ret_addr]
/ jmp function
과와 수익을 jmp rbx
,하지만 "합리적인"아니다. 콜 / 레트만큼 효율적이지 않으므로 실제 코드에서 찾을 수있는 것이 아닙니다.경계선 사례 : 함수 args로 처음 2 개의 요소가 주어지면 배열에서 시퀀스를 생성하는 함수를 작성하십시오 . 내가 선택한 배열에 발신자 저장소를 시퀀스의 시작을 그냥 배열에 대한 포인터를 전달합니다. 이것은 확실히 질문의 요구 사항을 굽히고 있습니다. 나는 args를 xmm0
위해 포장하는 것을 고려했다 movlps [rdi], xmm0
.
OS X 시스템 호출이이를 수행합니다 ( CF=0
오류 없음). 플래그 레지스터를 부울 리턴 값으로 사용하는 것이 좋지 않은 것으로 간주됩니까? .
하나의 JCC로 확인할 수있는 모든 조건은 특히 합리적이며 문제와 의미 적으로 관련된 조건을 선택할 수있는 경우에 특히 타당합니다. (예를 들어, 비교 함수는 플래그를 설정할 수 있으므로 jne
같지 않은 경우 사용됩니다).
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
.
x86-64 System V : 레지스터에 많은 인수를 전달하고 REX 접두사없이 사용할 수있는 많은 콜 클로저 레지스터가 있습니다. 더 중요한 것은 실제로 컴파일러가 쉽게 인라인 memcpy
또는 memset 을 허용하도록 선택되었습니다 rep movsb
. 처음 6 개의 정수 / 포인터 인수는 RDI, RSI, RDX, RCX, R8, R9로 전달됩니다.
함수 가 시간 을 실행하는 루프 내에서 lodsd
/ 명령어를 사용 하는 경우 ( 명령 과 함께 ) " x86-64 System V 호출 규칙과 같이 C에서 호출 가능"이라고 말할 수 있습니다 . 예 : 크로 마키 .stosd
rcx
loop
int 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 rax
2 바이트와 동일합니다 mov rax,rsp
여전히 수, 복사 2 바이트 전체 64 비트 레지스터.
ret 16
; 반환 주소를 표시하지 않고 배열을 누른 다음 push rcx
/을 누릅니다 ret
. 호출자는 배열 크기를 알고 있거나 스택 외부에 RSP를 저장하여 자체를 찾아야합니다.
AL / AX / EAX 및 기타 짧은 형식 및 단일 바이트 명령어에는 특수한 경우 짧은 형식 인코딩을 사용하십시오.
예에서는 기본 피연산자 크기가 32 비트 인 32/64 비트 모드를 가정합니다. 피연산자 크기 접두사는 명령어를 EAX 대신 AX로 변경합니다 (또는 16 비트 모드에서는 역).
inc/dec
레지스터 (8 비트 이외) : inc eax
/ dec ebp
. (x86-64 아님 : 0x4x
opcode 바이트는 REX 접두어로 용도 변경되었으므로 inc r/m32
유일한 인코딩입니다.)
opcode + ModR / M 피연산자 인코딩을inc bl
사용하여 8 비트 는 2 바이트 입니다. 그래서 사용 증가에 안전하게 있는지. (예 : 상위 바이트가 0이 아닌 경우 ZF 결과가 필요하지 않은 경우)inc r/m8
inc ebx
bl
scasd
: e/rdi+=4
는 레지스터가 읽을 수있는 메모리를 가리켜 야합니다. FLAGS 결과에 관심이없는 경우에도 유용합니다 (예 : cmp eax,[rdi]
/ rdi+=4
). 그리고 64 비트 모드에서, scasb
1 바이트로 동작 할 수inc rdi
lodsb 또는 stosb 유용하지 않은 경우.
xchg eax, r32
: 이것은 0x90 NOP가 온 곳 xchg eax,eax
입니다. 예 : / 대신에 / 의 남용을 포함하여 대부분의 명령어가 단일 바이트 인 경우 8 바이트의 GCD에 대해 / 루프 에 두 개의 xchg
명령어를 사용하여 3 개의 레지스터를 다시 정렬 합니다 .cdq
idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
: EAX를 EDX : EAX로 부호 확장합니다. 즉, EAX의 높은 비트를 EDX의 모든 비트에 복사합니다. 음이 아닌 것으로 알려진 0을 만들거나 0 / -1을 얻거나 더하거나 마스크로 가려면. x86 히스토리 레슨 : cltq
vs.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, rsp
REX 접두사가 필요하며 3 바이트입니다.
xlatb
존재하지만 거의 유용하지 않습니다. 큰 조회 테이블은 피해야합니다. 또한 AAA / DAA 또는 기타 압축 BCD 또는 2-ASCII 자리 명령어에 대한 사용을 찾지 못했습니다.
1 바이트 lahf
/ sahf
거의 유용하지 않습니다. 의 대안으로 / 할 수 는 있지만 일반적으로 유용하지 않습니다.lahf
and ah, 1
setc 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,eax
CF 및 EAX 지우기). 다른 조건 플래그에는 DF (문자열 방향) 및 IF (인터럽트)에 해당하는 명령어가 없습니다. 캐리 플래그는 많은 명령에 특별합니다. shifts가 설정하고 adc al, 0
2 바이트로 AL에 추가 할 수 있으며 앞에서 문서화되지 않은 SALC를 언급했습니다.
std
/ cld
거의 가치가 없어 보인다 . 특히 32 비트 코드에서, 더 나은 단지 사용에의 dec
포인터와에 mov
또는 메모리 소스 피연산자 대신 이렇게 DF를 설정하는 ALU 명령에 lodsb
/ stosb
아래 대신 최대의 이동합니다. 당신이 전혀 아래쪽으로 필요 일반적으로, 당신은 아직도 당신이 하나 이상 필요 했어, 그래서 또 다른 포인터가 올라가고 있습니다 std
및 cld
사용에 전체 기능에 lods
/ stos
모두. 대신 문자열 지시 사항을 위로 사용하십시오. (표준 호출 규칙은 함수 입력에서 DF = 0을 보장하므로을 사용하지 않고 무료로 가정 할 수 있습니다 cld
.)
원래 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/w
EAX를 즉시 사용할 수있는 모든 특수 케이스 인코딩에서 곱셈 / 나눗셈에도 암시적인 사용에 이르기 까지이 패러다임을 지원합니다 .
멀리 가지 마십시오. 특히 8 비트 대신 32 비트 레지스터로 즉시를 사용해야하는 경우 모든 것을 EAX로 바꾸는 것이 자동으로이기는 것은 아닙니다. 또는 레지스터의 여러 변수에 대한 작업을 한 번에 인터리브해야하는 경우. 또는 레지스터가 2 개인 명령어를 사용하는 경우에는 즉시 사용하지 마십시오.
그러나 항상 명심하십시오 : EAX / AL에서 더 짧은 것을하고 있습니까? AL에 이것을 갖도록 재 배열 할 수 있습니까, 또는 현재 이미 사용하고있는 AL을 AL보다 잘 활용하고 있습니까?
안전 할 때마다 8 비트 및 32 비트 작업을 자유롭게 혼합하여 활용할 수 있습니다 (풀 레지스터 등으로 수행 할 필요가 없음).
cdq
많은 경우에 div
제로가 필요한 edx
경우에 유용합니다 .
cdq
되지 않은 채로 남용 div
될 수 있습니다 eax
. 일반적으로 (외부 코드 골프)는 것 사용 cdq
을위한 설정으로 idiv
, 그리고 xor edx,edx
전div
fastcall
규칙x86 플랫폼에는 많은 호출 규칙이 있습니다. 레지스터에 매개 변수를 전달하는 것을 사용해야합니다. x86_64에서는 처음 몇 개의 매개 변수가 레지스터에 전달되므로 아무런 문제가 없습니다. 32 비트 플랫폼에서 기본 호출 규칙 ( cdecl
)은 매개 변수를 스택으로 전달하므로 골프에 적합하지 않습니다. 스택의 매개 변수에 액세스하려면 긴 명령이 필요합니다.
fastcall
32 비트 플랫폼에서 사용할 때 일반적으로 2 개의 첫 번째 매개 변수가 ecx
및에 전달됩니다 edx
. 함수에 3 개의 매개 변수가있는 경우 64 비트 플랫폼에서 구현하는 것을 고려할 수 있습니다.
fastcall
컨벤션을 위한 C 함수 프로토 타입 ( 이 예제 답변 에서 발췌 ) :
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU
0100 81C38000 ADD BX,0080
0104 83EB80 SUB BX,-80
마찬가지로 128 빼기 대신 -128을 더하십시오.
< 128
로를 <= 127
위한 즉각적인 피연산자의 크기를 줄이기 위해 cmp
, 또는 GCC는 항상 정리 선호 -129와 -128이 아닌 경우에도 크기를 줄이기 위해 비교합니다 .
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
.
CPU가 플랫폼 및 OS를 기반으로 알려져 있고 문서화 된 기본 상태에 있다고 가정 할 수 있습니다.
예를 들면 다음과 같습니다.
도스 http://www.fysnet.net/yourhelp.htm
리눅스 x86 ELF http://asm.sourceforge.net/articles/startup.html
_start
. 그렇습니다 . 함수 대신 프로그램을 작성하는 경우 에는이를 활용하는 것이 좋습니다. 나는 극단적 인 피보나치 에서 그렇게했다 . (동적으로 링크 된 실행 파일에서 실행 ld.so 점프하기 전에에 _start
, 그리고 않는 레지스터 휴가 쓰레기를하지만, 정적은 당신의 코드입니다.)
1을 더하거나 빼려면 멀티 바이트 추가 및 하위 명령어보다 작은 1 바이트 inc
또는 dec
명령어를 사용하십시오.
inc/dec r32
opcode로 인코딩 된 레지스터 번호와 함께 1 바이트 가 있습니다. 따라서 inc ebx
1 바이트이지만 inc bl
2 add bl, 1
입니다. 물론 이외 의 레지스터의 경우 여전히 작 습니다 al
. 또한 점에 유의 inc
/ dec
CF가 수정되지 않은두고 있지만, 다른 플래그를 업데이트합니다.
lea
수학이것은 아마도 x86에 대해 가장 먼저 배우는 것 중 하나 일 것입니다. lea
2, 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
계산 에도 사용할 수 있습니다 .
lea eax, [rcx + 13]
64 비트 모드에 대한 노 여분 접두사 버전입니다. 32 비트 피연산자 크기 (결과) 및 64 비트 주소 크기 (입력)
mov
적용 가능한 경우 하위 레지스터에 작은 즉시레지스터의 상위 비트가 0임을 이미 알고 있다면, 더 짧은 명령어를 사용하여 즉시 하위 레지스터로 이동할 수 있습니다.
b8 0a 00 00 00 mov $0xa,%eax
대
b0 0a mov $0xa,%al
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
많은 상수가 필요한 경우 유용 할 수 있습니다 .
[0x80..0xFF]
확장 부호 imm8로 표현할 수 없습니다. 또는 예를 들어 명령 후 상위 바이트 를 이미 알고 있다면 점프하지 않는 유일한 방법 은 언제 만들어 졌는지 입니다. (당신 이 이것을 말한 것 같지만, 당신의 예는을 사용합니다 ). 끝났을 때 다른 것이 레지스터를 0 (또는 무엇이든)으로 되돌려 놓는 한 다른 바이트의 레지스터를 사용할 수도 있습니다. 예를 들어 내 피보나치 프로그램 은 ebx를 유지 하고 bl을 사용합니다. mov cl, 0x10
loop
loop
rcx=0
xor
-1024
xchg eax, r32
예 : mov bl, 10
/ dec bl
/ 제외 ) jnz
코드는 높은 바이트의 RBX를 신경 쓰지 않습니다.
많은 산술 명령어 후에 캐리 플래그 (서명되지 않음) 및 오버플로 플래그 (서명 됨)가 자동으로 설정됩니다 ( 추가 정보 ). Sign Flag 및 Zero Flag는 많은 산술 및 논리 연산 후에 설정됩니다. 조건부 분기에 사용할 수 있습니다.
예:
d1 f8 sar %eax
ZF는이 명령어에 의해 설정되므로 조건 분기에 사용할 수 있습니다.
test al,1
; 일반적으로 무료로 얻지 못합니다. (또는 and al,1
홀수 / 짝수에 따라 정수 0/1을 만들려면)
test
/ 를 피하기 위해 다른 명령에 의해 이미 설정된 플래그를 사용하십시오"라고 말하면 cmp
, 그것은 매우 기본적인 초보자 x86이지만 여전히 공감할만한 가치가 있습니다.
x86에만 해당되는 것은 아니지만 널리 적용되는 초보자 조립 팁입니다. while 루프가 적어도 한 번 실행된다는 것을 알고 있다면 루프 조건 점검이 끝날 때 루프를 do-while 루프로 다시 쓰면 종종 2 바이트 점프 명령이 저장됩니다. 특별한 경우에는을 사용할 수도 있습니다 loop
.
do{}while()
어셈블리에서 자연 반복되는 관용구가 왜 (특히 효율성을 위해) 있는지 설명합니다 . 또한 2 바이트 jecxz
/ jrcxz
루프 전 loop
은 "제로 실행 필요"사례를 "효율적으로"처리하는 데 매우 효과적입니다 ( loop
느린 CPU 가 아닌 경우). jecxz
또한 사용 가능한 내부 루프가를 구현하기 위해while(ecx){}
함께 jmp
바닥에.
시스템 V 86 스택을 사용하고 시스템 V - 64 용도 rdi
, rsi
, rdx
, rcx
, 등의 입력 매개 변수 및 rax
반환 값으로,하지만 자신의 호출 규칙을 사용하여 완벽하게 합리적이다. __fastcall 은 ecx
및 edx
입력 매개 변수로 사용 하고 다른 컴파일러 / OS는 자체 규칙을 사용합니다 . 편리한 경우 스택 및 모든 레지스터를 입력 / 출력으로 사용하십시오.
예 : 1 바이트 솔루션에 대한 영리한 호출 규칙을 사용하는 반복 바이트 카운터
메타 : 레지스터에 입력 쓰기 , 레지스터 에 출력 쓰기
기타 리소스 : 전화 회의에 대한 Agner Fog의 메모
int 0x80
은 많은 설정이 필요합니다.
int 0x80
32 비트 코드 또는 syscall
64 비트 코드에서를 호출하는 sys_write
것이 유일한 방법입니다. 내가 Extreme Fibonacci에 사용한 것입니다 . 64 비트 코드에서 __NR_write = 1 = STDOUT_FILENO
, 당신은 할 수 있습니다 mov eax, edi
. 또는 mov al, 4
32 비트 코드에서 EAX의 상위 바이트가 0 인 경우 . 당신은 또한 수 call printf
또는 puts
, 내가 생각하고, 대답은 "리눅스 + glibc에 대한 86 ASM"을 작성합니다. PLT 또는 GOT 입력 공간 또는 라이브러리 코드 자체를 계산하지 않는 것이 합리적이라고 생각합니다.
char*buf
수동 형식으로 문자열을 생성하게하는 경향이 있습니다 . 이와 같은 예 (어색 속도 최적화) ASM FizzBuzz I 레지스터에 문자열 데이터를 얻었고, 그 다음에 그것을 저장 mov
문자열이 짧고 고정 길이 때문이었다.
CMOVcc
및 세트 사용SETcc
이것은 나 자신에게 상기시켜주는 것이지만, 조건부 설정 명령이 존재하고 조건부 이동 명령이 프로세서 P6 (Pentium Pro) 이상에 존재합니다. EFLAGS에 설정된 하나 이상의 플래그를 기반으로하는 많은 명령이 있습니다.
cmov
2 바이트 opcode ( 0F 4x +ModR/M
)가있어 최소 3 바이트 인 경우가 있습니다. 그러나 소스는 r / m32이므로 조건부로 3 바이트로로드 할 수 있습니다. 분기 이외의 다른 setcc
경우보다 유용합니다 cmovcc
. 여전히 기준선 386 명령어가 아니라 전체 명령어 세트를 고려하십시오. (SSE2 및 BMI / BMI2 명령어는 너무 커서 거의 유용하지 않습니다. rorx eax, ecx, 32
mov + ror보다 6 바이트 더 깁니다. POPCNT 또는 PDEP가 많은 것을 저장하지 않는 한 골프는 아닙니다. 성능은 훌륭합니다)
setcc
.
jmp
if / 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
.
cmp
을 수행 하여 를 제거 할 수 있습니다 sub $'A'-10, %al
. jae .was_alpha
; add $('A'-10)-'0'
. (논리가 맞다고 생각합니다). 참고 'A'-10 > '9'
때문에 모호함이 없다. 문자에 대한 수정 값을 빼면 10 진수가 줄 바꿈됩니다. 따라서 입력이 유효한 16 진수라고 가정하면 안전합니다.
esi를 esp로 설정하고 일련의 lodsd / xchg reg, eax를 수행하여 스택에서 순차 오브젝트를 페치 할 수 있습니다.
pop eax
/ pop edx
/ ... 보다 낫 습니까? 스택에 그대로 두어야하는 경우 push
ESP를 복원 한 후에도 개체 당 2 바이트 씩 복원 할 수 있습니다 mov esi,esp
. 또는 64 바이트 코드에서 pop
8 바이트를 얻는 4 바이트 객체를 의미 했 습니까? BTW, 당신도 사용할 수 있습니다 pop
보다 더 나은 성능을 버퍼를 통해 루프에 lodsd
대한 예를 들어, 익스트림 피보나치 확장 된 정밀도 추가
64 비트 레지스터를 복사하려면 push rcx
; pop rdx
3 바이트 대신 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
이면 mov
push 및 / 또는 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은 실제로 교환하는 데 도움이되지 않습니다.
push 200; pop edx
-3 바이트를 사용하십시오 .