回答:
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レジスタのすべてのビットを1に効率的に設定するも参照してください。
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バイト)を置き換えることさえできます。参照して効率的に1にCPUレジスタのすべてのビットを設定してくださいのようなもののためlea eax, [rcx-1]
にある与えられた既知の値eax
(例えば必要性ゼロ化されたレジスタの場合および /ポップ別の定数、代わりにプッシュだけ使用LEA
多くの場合、アキュムレータベースの命令(つまり(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
、EAXの下位バイトについてテストするために使用します。たとえば、lodsd
/ test al, 1
/ setnz cl
は奇数/偶数の場合はcl = 1または0になります。しかし、その後、あなたが32ビット即値必要まれなケース、確認中op eax, imm32
のように、私のクロマキーアンサー
答えの言語はasm(実際はマシンコード)です。したがって、C-compiled-for-x86ではなくasmで記述されたプログラムの一部として扱います。 関数は、標準の呼び出し規約を使用してCから簡単に呼び出すことができる必要はありません。ただし、余分なバイト数がかからない場合、これは素晴らしいボーナスです。
純粋なasmプログラムでは、一部のヘルパー関数は、それらと呼び出し元にとって便利な呼び出し規約を使用するのが普通です。このような関数は、呼び出し規約(入力/出力/クラッバー)をコメントで文書化します。
実際には、asmプログラムでも(ほとんどのソースファイル間で)ほとんどの関数で一貫した呼び出し規則を使用する傾向がありますが、重要な関数は何か特別なことができます。code-golfでは、1つの関数のがらくたを最適化するので、明らかにそれは重要/特別です。
Cプログラムから関数をテストするには、引数を適切な場所に配置し、クローバーする余分なレジスタを保存/復元し、戻り値e/rax
がまだない場合はその中に戻り値を入れるラッパーを作成できます。
DF(lods
/ stos
/などの文字列方向フラグ)を呼び出し/ retでクリア(上方向)にすることは正常です。call / retで未定義にするのは問題ありません。エントリでクリアまたは設定する必要がありますが、戻ったときに変更したままにしておくのは奇妙です。
x87でFP値を返すことst0
は妥当ですがst3
、他のx87レジスタでゴミを入れて返すことはできません。呼び出し元はx87スタックをクリーンアップする必要があります。st0
空ではないより高いスタックレジスタで戻ることも疑問です(複数の値を返す場合を除く)。
call
ため[rsp]
、返信先アドレスも呼び出されます。/ などのリンクレジスタを使用してx86で/ を回避し、で返すことができますが、それは「合理的」ではありません。それはcall / retほど効率的ではないので、実際のコードでもっともらしく見つけることはできません。call
ret
lea rbx, [ret_addr]
jmp function
jmp rbx
境界線の場合:最初の2つの要素を関数argsとして指定して、配列にシーケンスを生成する関数を作成します。 私が選んだの配列に、発信者の店にシーケンスの開始を持っているだけで、配列へのポインタを渡します。これは間違いなく質問の要件を曲げています。xmm0
for movlps [rdi], xmm0
にパックされた引数を取ることを検討しましたが、これも奇妙な呼び出し規約になります。
OS Xシステムコールはこれを行います(CF=0
エラーがないことを意味します)。フラグレジスタをブール値の戻り値として使用するのは悪い習慣と見なされますか 。
1つのJCCでチェックできる条件は完全に合理的です。特に問題に意味的な関連性がある条件を選択できる場合はそうです。(たとえば、比較関数はフラグを設定する可能性があるためjne
、それらが等しくなかった場合に取得されます)。
char
)を符号または32ビットまたは64ビットにゼロ拡張する必要があります。これは不合理ではありません。部分的なレジスタのスローダウンを使用しmovzx
たりmovsx
、回避したりすることは、最新のx86 asmでは普通です。:実際の打ち鳴らすに/ LLVMは既にx86-64のシステムV呼び出し規約に文書化されていない拡張に依存するコードを行う32ビットより狭い引数は、呼び出し側によって32ビットに符号拡張またはゼロです。
必要に応じて、プロトタイプを作成するuint64_t
かint64_t
、作成することにより、64ビットへの拡張機能を文書化/記述できます。たとえば、loop
命令を使用できます。命令は、アドレスサイズプレフィックスを使用してサイズを32ビットECXまでオーバーライドしない限り、RCXの64ビット全体を使用します(実際には、オペランドサイズではなくアドレスサイズ)。
これlong
は、Windows 64ビットABIおよびLinux x32 ABIでは 32ビットタイプのみであることに注意してください。uint64_t
は明確であり、入力よりも短いunsigned long long
。
Windows 32ビット__fastcall
、すでに別の答えによって提案された整数の引数で:ecx
とedx
。
x86-64 System V:多くの引数をレジスタに渡し、REXプレフィックスなしで使用できるコールクローバーレジスタをたくさん持っています。さらに重要なことは、実際にコンパイラーがインラインmemcpy
またはmemsetをrep movsb
簡単にインライン化できるように選択されたことです。最初の6つの整数/ポインター引数は、RDI、RSI、RDX、RCX、R8、R9で渡されます。
関数が(命令を使用して)時間を実行するループ内でlodsd
/ stosd
を使用する場合、「x86-64 System V呼び出し規約と同様にCから呼び出し可能」と言うことができます。 例:chromakey。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の例)。レジスタにゼロ拡張された32ビットの負の整数がない限り、64ビットのアドレスサイズを引き続き使用できます(そうすると、大きな符号なしの値になります[rdi + rdx]
)。
push rsp
/ pop rax
は2バイトで、と同等であるためmov rax,rsp
、2バイトで完全な64ビットレジスタをコピーできることに注意してください。
ret 16
;でポップします。リターンアドレスをポップせず、配列をプッシュしてから、push rcx
/をプッシュしret
ます。呼び出し元は、配列サイズを知っているか、スタックの外側のどこかにRSPを保存して自分自身を見つける必要があります。
AL / AX / EAX、およびその他の短い形式とシングルバイト命令に特別な場合の短い形式のエンコードを使用する
例では、32/64ビットモードを想定しています。デフォルトのオペランドサイズは32ビットです。オペランドサイズのプレフィックスは、命令をEAXではなくAXに変更します(または16ビットモードでは逆になります)。
inc/dec
レジスタ(8ビット以外):inc eax
/ dec ebp
。(x86-64ではありません:0x4x
オペコードバイトはREXプレフィックスとして再利用されたためinc r/m32
、唯一のエンコーディングです。)
8ビットinc bl
は2バイトで、inc r/m8
オペコード+ ModR / Mオペランドエンコーディングを使用します。したがって、安全であれば、を使用inc ebx
してインクリメントしますbl
。(たとえば、上位バイトがゼロ以外の場合にZFの結果が必要ない場合)。
scasd
:e/rdi+=4
、レジスタが読み取り可能なメモリを指す必要があります。FLAGSの結果を気にしなくても役立つ場合があります(cmp eax,[rdi]
/などrdi+=4
)。また、64ビットモードでscasb
はinc rdi
、lodsbまたはstosbが役に立たない場合は1バイトとして機能します。
xchg eax, r32
:これは、0x90 NOPの由来ですxchg eax,eax
。例:GCDの / ループxchg
内の2つの命令で3つのレジスタを8バイトで再配置します。ほとんどの命令はシングルバイトで、/の代わりに/の不正使用を含みますcdq
idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
:EAXをEDX:EAXに符号拡張します。つまり、EAXの上位ビットをEDXのすべてのビットにコピーします。既知の非負のゼロを作成するか、追加/サブまたはマスクする0 / -1を取得します。 x86ヒストリーレッスン:cltq
vsmovslq
.およびAT&T vs. Intelニーモニック(これと関連する)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アスキー数字命令の使用法を見つけたことがありません。
1バイトlahf
/ sahf
はめったに役立ちません。/ の代わりに/ を使用でき ますが、通常は役に立ちません。lahf
and ah, 1
setc ah
また、特にCFについてsbb eax,eax
は、0 / -1を取得するか、ドキュメント化されていないが普遍的にサポートされている1バイトsalc
(キャリーからALを設定)を取得する必要がありsbb al,al
ます。(x86-64で削除)。ユーザー感謝チャレンジ#1:デニス♦で SALCを使用しました。
1バイトのcmc
/ clc
/ stc
(flip( "complement")、clear、またはset CF)はほとんど役に立たないが、ベース10 ^ 9チャンクでの拡張精度加算での使用をcmc
見つけた。CFを無条件に設定/クリアするには、通常、それを別の命令の一部として実行するように調整しxor eax,eax
ます。たとえば、CFとEAXをクリアします。他の条件フラグに相当する命令はなく、DF(文字列方向)とIF(割り込み)のみがあります。キャリーフラグは、多くの命令にとって特別です。シフトはそれを設定し、adc al, 0
2バイトでALに追加できます。また、以前に文書化されていないSALCについて言及しました。
std
/ cld
めったに価値がないと思われる。特に32ビットコードでは、より良いだけ使用へだdec
ポインタ上及びmov
またはメモリソースオペランドALU命令に代えてDFを設定するようにlodsb
/ stosb
代わりに最大の下方へ行きます。あなたがすべてで下方必要がある場合は、1個以下の必要があると思いますので、通常、あなたはまだ、上がって別のポインタを持っているstd
し、cld
使用する関数全体でlods
/ stos
の両方のために。代わりに、上方向の文字列命令を使用してください。(標準の呼び出し規約では、関数のエントリでDF = 0が保証されているため、を使用せずに無料で想定できますcld
。)
同様の手順:オリジナル8086では、AXは非常に特別だったlodsb
/ stosb
、cbw
、mul
/ div
などが暗黙のうちにそれを使用しています。もちろん、それはもちろんです。現在のx86は8086のオペコード(少なくとも公式に文書化されたオペコードのどれも)をドロップしていません。しかし、後にCPUが新しい命令を追加して、最初にAXにコピーまたはスワップすることなく、より良い/より効率的な方法を実現しました。(または32ビットモードでEAXに。)
例えば、8086には、ロードまたは移動+符号拡張、または上位半分の結果を生成せず、暗黙のオペランドを持たない2および3オペランドのmovsx
/のような後の追加がありませんでした。movzx
imul cx, bx, 1234
また、8086の主なボトルネックは命令フェッチであったため、当時のパフォーマンスにとってコードサイズの最適化は重要でした。8086 のISAデザイナー(Stephen Morse)は、すべての基本的なimmediate-src ALU-命令の特別な(E)AX / AL-destinationオペコードを含む、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
。
lodsb/w
EAXでのイミディエートのすべての特殊なケースエンコーディングから乗算/除算までの暗黙的な使用まで、8086命令エンコーディングに関するすべてがこのパラダイムをサポートしています。
夢中にならないでください。特に8ビットではなく32ビットのレジスタでイミディエイトを使用する必要がある場合は、すべてをEAXにスワップすることは自動的に勝利ではありません。または、レジスタ内の複数の変数に対する操作を一度にインターリーブする必要がある場合。または、イミディエイトではなく、2つのレジスターを持つ命令を使用している場合。
ただし、常に念頭に置いてください。EAX/ 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とゼロを取得する)で3つのゼロを作成します3番目のレジスタでゼロを乗算することにより、eaxとedxをゼロにできます。
xor ebx, ebx ; 2B ebx = 0
mul ebx ; 2B eax=edx = 0
inc ebx ; 1B ebx=1
EAX、EDX、およびEBXがすべて4バイトでゼロになります。3バイトでEAXとEDXをゼロにすることができます。
xor eax, eax
cdq
しかし、その開始点から、もう1バイトで3番目にゼロ化されたレジスターを取得することも、別の2バイトで+1または-1レジスターを取得することもできません。代わりに、mulテクニックを使用してください。
ユースケースの例:フィボナッチ数をバイナリに連結します。
LOOP
ループが終了すると、ECXはゼロになり、EDXとEAXをゼロにするために使用できることに注意してください。で最初のゼロを作成する必要はありませんxor
。
CPUは、プラットフォームとOSに基づいて、既知の文書化されたデフォルト状態にあると想定できます。
例えば:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
_start
ます。そのため、関数ではなくプログラムを作成している場合、それを利用するのは公正なゲームです。私はエクストリームフィボナッチでそうしました。(動的にリンクされた実行可能ファイルで、実行ld.soジャンプの前にあなたに_start
、そしてないレジスタの休暇のゴミを、しかし、静的なだけであなたのコードである。)
1を加算または減算するには、マルチバイトの加算およびサブ命令よりも小さい1バイトinc
またはdec
命令を使用します。
inc/dec r32
、レジスタ番号がオペコードでエンコードされた1バイトがあることに注意してください。したがってinc ebx
、1バイトですがinc bl
、2です。add bl, 1
もちろん、それ以外のレジスタの場合は、それよりも小さくなりal
ます。また、inc
/ dec
はCFを変更せずに残しますが、他のフラグは更新します。
lea
数学用これはおそらくx86について最初に学ぶことの1つですが、ここでは念のために残しておきます。lea
2、3、4、5、8、または9で乗算を行い、オフセットを追加するために使用できます。
たとえばebx = 9*eax + 3
、1つの命令で計算するには(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ビットのアドレスサイズ(入力用)。
ループおよび文字列命令は、代替命令シーケンスよりも小さくなっています。最も有用ですloop <label>
2つの命令シーケンスよりも小さいdec ECX
とjnz <label>
、そしてlodsb
よりも小さくなっているmov al,[esi]
とinc si
。
mov
該当する場合、下位レジスタへの小さな即値レジスタの上位ビットが0であることが既にわかっている場合は、短い命令を使用して、即値を下位レジスタに移動できます。
b8 0a 00 00 00 mov $0xa,%eax
対
b0 0a mov $0xa,%al
push
/ pop
を使用して上位ビットをゼロにするPeter Cordesの功績。xor
/ mov
は4バイトですが、push
/ pop
は3だけです!
6a 0a push $0xa
58 pop %eax
mov al, 0xa
完全なregにゼロ拡張する必要がない場合に適しています。しかし、そうすると、xor / movは4バイトであるのに対して、プッシュimm8 / popまたはlea
他の既知の定数からは3バイトです。これは、組み合わせに有用であり得るとmul
ゼロ3 4バイトのレジスタへ、またはcdq
かかわらず、あなたは定数の多くを必要とする場合は、。
[0x80..0xFF]
、からの定数で、これは符号拡張されたimm8として表現できません。または、例えば命令の後など、すでに上位バイトを知っている場合は、ジャンプしない唯一の方法は作成されたときです。(あなたはこれを言ったと思いますが、あなたの例ではを使用しています)。レジスタの下位バイトを他の何かに使用することもできます。ただし、完了時に他の何かがゼロ(または何でも)に戻す限りです。たとえば、私のフィボナッチプログラムはebxを保持し、blを使用します。mov cl, 0x10
loop
loop
rcx=0
xor
-1024
xchg eax, r32
)より多くのもの(たとえば、mov bl, 10
/ dec bl
/ )を使用することをお勧めしjnz
ます。
多くの算術命令の後、キャリーフラグ(符号なし)およびオーバーフローフラグ(符号付き)が自動的に設定されます(詳細)。符号フラグとゼロフラグは、多くの算術演算と論理演算の後に設定されます。これは条件分岐に使用できます。
例:
d1 f8 sar %eax
ZFはこの命令によって設定されるため、条件分岐に使用できます。
test al,1
。通常は無料で入手できません。(またはand al,1
奇数/偶数に応じて整数0/1を作成します。)
test
/ を避けるために他の命令によって既に設定されたフラグを使用する」と言った場合cmp
、それはかなり基本的な初心者のx86ですが、それでも賛成に値します。
これはx86固有ではありませんが、広く適用可能な初心者向けアセンブリのヒントです。whileループが少なくとも1回実行されることがわかっている場合、ループをdo-whileループとして書き換え、最後にループ条件チェックを行うと、多くの場合2バイトのジャンプ命令が節約されます。特別な場合には、を使用することもできますloop
。
do{}while()
アセンブリの自然なループイディオムが(特に効率のため)なぜであるかを説明します。注また、2バイトのことjecxz
/ jrcxz
ループは非常によく働く前にloop
(まれCPU上「効率的」の場合、「ゼロ回を実行する必要がある」処理するためにloop
遅いではありませんが)。 使用可能な内部ループを実装すると、下部に。jecxz
while(ecx){}
jmp
System Vのx86のは、スタックを使用し、System Vのx86-64の用途はrdi
、rsi
、rdx
、rcx
、などの入力パラメータについて、およびrax
戻り値として、あなた自身の呼び出し規約を使用して完全に合理的です。__fastcallはecx
and 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の上位バイトがゼロの場合。call printf
またはputs
、「Linux + glibc用のx86 asm」という回答を書くこともできます。PLTやGOTのエントリスペース、またはライブラリコード自体をカウントしないのが妥当だと思います。
char*buf
を渡して、その中に文字列を手動でフォーマットさせて生成させたいと思います。たとえば、このように(速度に対して不便に最適化された)asm FizzBuzzの場合、文字列データがレジスタに格納され、それを使用して保存されmov
ます。
CMOVcc
とセットを使用するSETcc
これは私自身を思い出させるものですが、条件付きセット命令が存在し、条件付き移動命令がプロセッサP6(Pentium Pro)以降に存在します。EFLAGSで設定された1つ以上のフラグに基づいた多くの命令があります。
cmov
が、2バイトのオペコード(0F 4x +ModR/M
)があるため、最小で3バイトです。ただし、ソースはr / m32であるため、条件付きで3バイトでロードできます。分岐以外にsetcc
、より多くの場合に役立ちcmovcc
ます。それでも、ベースラインの386命令だけでなく、命令セット全体を検討してください。(SSE2およびBMI / BMI2命令は、彼らはめったに便利だほど大きいですが rorx eax, ecx, 32
、MOV + RORよりも長く、6バイトであるニースは、パフォーマンスのために、ではないゴルフがPOPCNTまたはPDEPは多くのISNを保存しない限り。)
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
1つのケースのクリティカルパスでの余分なレイテンシがループキャリーの依存関係チェーンの一部ではない場合(各入力桁が4ビットチャンクをマージするまで独立している場合) )。しかし、とにかく+1。ところで、あなたの例には最適化されていない個別の最適化があります:movzx
最後に必要な場合は、sub $imm, %al
EAXを使用せずにの非modrm 2バイトエンコーディングを利用してください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ビットコードの4バイトオブジェクトの場合、pop
8バイトを取得するという意味ですか?ところで、たとえば、Extreme Fibonacciで拡張精度を追加するpop
場合などlodsd
、より優れたパフォーマンスでバッファをループすることもできます。
64ビットレジスタをコピーするにはpush rcx
、;を使用します。pop rdx
3バイトの代わりにmov
。
push / popのデフォルトのオペランドサイズは64ビットで、REXプレフィックスは不要です。
51 push rcx
5a pop rdx
vs.
48 89 ca mov rdx,rcx
(オペランドサイズのプレフィックスはプッシュ/ポップサイズを16ビットにオーバーライドできますが、32ビットのプッシュ/ポップオペランドサイズは、REX.W = 0であっても64ビットモードでエンコードできません。)
どちらかまたは両方のレジスタがr8
.. r15
である場合mov
、プッシュおよび/またはポップにはREXプレフィックスが必要になるため、使用します。最悪の場合、両方にREXプレフィックスが必要な場合、実際にこれは失われます。明らかに、通常、コードゴルフではr8..r15を避ける必要があります。
このNASMマクロを使用して開発している間、ソースをより読みやすく保つことができます。RSPの下の8バイトをステップ実行することを覚えておいてください。(x86-64 System 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 200; pop edx
-3バイトなどを使用します。