mov
-immediate ist teuer für Konstanten
Das mag offensichtlich sein, aber ich werde es trotzdem hier platzieren. Im Allgemeinen lohnt es sich, über die Darstellung einer Zahl auf Bitebene nachzudenken, wenn Sie einen Wert initialisieren müssen.
Initialisierung eax
mit 0
:
b8 00 00 00 00 mov $0x0,%eax
sollte gekürzt werden ( aus Gründen der Leistung sowie der Codegröße ) auf
31 c0 xor %eax,%eax
Initialisierung eax
mit -1
:
b8 ff ff ff ff mov $-1,%eax
kann auf gekürzt werden
31 c0 xor %eax,%eax
48 dec %eax
oder
83 c8 ff or $-1,%eax
Im Allgemeinen kann jeder vorzeichenerweiterte 8-Bit-Wert in 3 Bytes mit push -12
(2 Bytes) / pop %eax
(1 Byte) erstellt werden. Dies funktioniert sogar für 64-Bit-Register ohne zusätzliches REX-Präfix. push
/ pop
default Operandengröße = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Oder wenn Sie eine bekannte Konstante in einem Register haben, können Sie mit lea 123(%eax), %ecx
(3 Byte) eine weitere Konstante in der Nähe erstellen . Dies ist praktisch, wenn Sie ein Nullregister und eine Konstante benötigen . xor-zero (2 Bytes) + lea-disp8
(3 Bytes).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Siehe auch Alle Bits im CPU-Register effizient auf 1 setzen
push 200; pop edx
zu initialisieren, verwenden Sie zB - 3 Byte für die Initialisierung.dec
zBxor eax, eax; dec eax
push imm8
/pop reg
ist 3 Bytes und ist fantastisch für 64-Bit-Konstanten auf x86-64, wobeidec
/inc
2 Bytes ist. Undpush r64
/pop 64
(2 Bytes) kann sogar 3 Bytes ersetzenmov r64, r64
(3 Bytes mit REX). Siehe auch Setzen Sie alle Bits in CPU - Register auf 1 effizient für Sachen wielea eax, [rcx-1]
angegeben einen bekannten Wert ineax
(zB bei Bedarf ein Null gesetzten Register und eine weitere Konstante, LEA nur Gebrauch statt Push / PopIn vielen Fällen sind akkumulatorbasierte Befehle (dh Befehle, die
(R|E)AX
als Zieloperanden dienen) 1 Byte kürzer als allgemeine Befehle. Siehe diese Frage auf StackOverflow.quelle
al, imm8
Sonderfälle am nützlichsten , zBor al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ja .non_alphabetic
mit jeweils 2 Bytes anstelle von 3. Die Verwendungal
für Zeichendaten ermöglicht auchlodsb
und / oderstosb
. Oder Gebrauchal
zu testen , etwas über den Low - Byte von EAX, wielodsd
/test al, 1
/setnz cl
cl = 1 oder 0 für ungerade macht / sogar. Aber in dem seltenen Fall, in dem Sie eine 32-Bit-Sofortversion benötigen, dann sicherop eax, imm32
, wie in meiner Chroma-Key-AntwortWählen Sie Ihre Anrufkonvention, um die Argumente an die von Ihnen gewünschte Stelle zu setzen.
Die Sprache Ihrer Antwort ist asm (tatsächlich Maschinencode). Behandeln Sie sie daher als Teil eines in asm geschriebenen Programms, nicht in C-compiled-for-x86. Ihre Funktion muss mit keiner Standard-Aufrufkonvention von C aus leicht aufrufbar sein. Das ist aber ein schöner Bonus, wenn es Sie keine zusätzlichen Bytes kostet.
In einem reinen asm-Programm ist es normal, dass einige Hilfsfunktionen eine für sie und ihren Aufrufer geeignete Aufrufkonvention verwenden. Solche Funktionen dokumentieren ihre Aufrufkonvention (Ein- / Ausgänge / Clobber) mit Kommentaren.
Im wirklichen Leben tendieren sogar ASM-Programme (glaube ich) dazu, konsistente Aufrufkonventionen für die meisten Funktionen zu verwenden (insbesondere für verschiedene Quelldateien), aber jede wichtige Funktion könnte etwas Besonderes bewirken. Beim Code-Golf optimieren Sie den Mist aus einer einzigen Funktion heraus, es ist also offensichtlich wichtig / besonders.
Um Ihre Funktion in einem C-Programm zu testen, können Sie einen Wrapper schreiben , der args an die richtigen Stellen setzt, alle zusätzlichen Register speichert / wiederherstellt, die Sie überladen, und den Rückgabewert dort ablegen,
e/rax
wo er noch nicht vorhanden war.Die Grenzen des Zumutbaren: Alles, was dem Anrufer keine unzumutbare Last auferlegt:
Es ist normal, dass DF (Zeichenfolgenrichtungsflag für
lods
/stos
/ usw.) beim Aufrufen / Zurückrufen (nach oben) frei sein muss. Es wäre in Ordnung, wenn es beim Anrufen / Zurückrufen undefiniert wäre. Es wäre seltsam, wenn es beim Betreten gelöscht oder eingestellt werden müsste, aber bei der Rückkehr geändert werden müsste.Die Rückgabe von FP-Werten in x87
st0
ist sinnvoll, die Rückgabest3
mit Garbage in einem anderen x87-Register jedoch nicht. Der Aufrufer müsste den x87-Stack aufräumen. Auch die Rückkehrst0
mit nicht leeren höheren Stack-Registern wäre fraglich (es sei denn, Sie geben mehrere Werte zurück).call
, ebenso[rsp]
Ihre Absenderadresse. Sie könnencall
/ret
auf x86 vermeiden, indem Sie Linkregister wielea rbx, [ret_addr]
/ verwendenjmp function
und mit zurückkehrenjmp rbx
, aber das ist nicht "vernünftig". Das ist nicht so effizient wie call / ret, es ist also nichts, was man in echtem Code plausibel finden würde.Grenzfälle: Schreiben Sie eine Funktion, die eine Sequenz in einem Array erzeugt, wenn die ersten beiden Elemente als Funktionsargumente angegeben werden . Ich habe mich dafür entschieden , dass der Aufrufer den Beginn der Sequenz im Array speichert und nur einen Zeiger auf das Array übergibt. Dies ist definitiv eine Biegung der Anforderungen der Frage. Ich betrachtete die args Einnahme verpackt in
xmm0
fürmovlps [rdi], xmm0
, die auch eine seltsame Aufrufkonvention wäre.Rückgabe eines Booleschen Wertes in FLAGS (Bedingungscodes)
OS X-Systemaufrufe führen dies aus (
CF=0
bedeutet, dass kein Fehler vorliegt): Wird die Verwendung des Flags-Registers als boolescher Rückgabewert als schlechte Praxis angesehen? .Jede Bedingung, die mit einem JCC überprüft werden kann, ist völlig zumutbar, insbesondere wenn Sie eine Bedingung auswählen können, die für das Problem semantisch relevant ist. (Zum Beispiel kann eine Vergleichsfunktion Flags setzen,
jne
die verwendet werden, wenn sie nicht gleich sind.)Es ist erforderlich, dass schmale Args (wie a
char
) ein Vorzeichen oder eine Null sind, die auf 32 oder 64 Bit erweitert ist.Das ist nicht unvernünftig. Die Verwendung von
movzx
odermovsx
zur Vermeidung von Teilregister-Verlangsamungen ist in modernen x86-Umgebungen normal. Tatsächlich erstellt clang / LLVM bereits Code, der von einer undokumentierten Erweiterung der x86-64-System-V-Aufrufkonvention abhängt: Argumente, die schmaler als 32 Bit sind, werden vom Aufrufer mit Vorzeichen oder Null auf 32 Bit erweitert .Sie können die Erweiterung auf 64 Bit schriftlich
uint64_t
oderint64_t
in Ihrem Prototyp dokumentieren / beschreiben, wenn Sie möchten. Sie können also einenloop
Befehl verwenden, der die gesamten 64 Bits von RCX verwendet, es sei denn, Sie verwenden ein Adressgrößenpräfix, um die Größe auf 32-Bit-ECX herabzusetzen (ja, tatsächlich, Adressgröße nicht Operandengröße).Beachten Sie, dass
long
es sich beim Windows 64-Bit-ABI und beim Linux x32-ABI nur um einen 32-Bit-Typ handelt .uint64_t
ist eindeutig und kürzer alsunsigned long long
.Bestehende Anrufkonventionen:
Windows 32-Bit
__fastcall
, bereits von einer anderen Antwort vorgeschlagen : Integer-Argumente inecx
undedx
.x86-64-System V : Übergibt viele Argumente in Registern und verfügt über viele Call-Clobbered-Register, die Sie ohne REX-Präfixe verwenden können. Noch wichtiger ist, dass Compiler
memcpy
sorep movsb
einfach inline oder memset arbeiten können: Die ersten 6 Integer / Pointer-Args werden in RDI, RSI, RDX, RCX, R8, R9 übergeben.Wenn Ihre Funktion
lodsd
/stosd
in einer Schleife verwendet, diercx
(mit derloop
Anweisung) Zeiten ausführt, können Sie sagen, dass "von C wieint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
mit der x86-64-System V-Aufrufkonvention aufrufbar " ist. Beispiel: Chromakey .32-Bit-GCC
regparm
: Ganzzahlige Argumente in EAX , ECX, EDX, Rückgabe in EAX (oder EDX: EAX). Das erste Argument im selben Register wie der Rückgabewert zu haben, ermöglicht einige Optimierungen, wie in diesem Fall mit einem Beispielaufrufer und einem Prototyp mit einem Funktionsattribut . Und natürlich ist AL / EAX speziell für einige Anweisungen.Das Linux x32-ABI verwendet im Langmodus 32-Bit-Zeiger, sodass Sie beim Ändern eines Zeigers ein REX-Präfix speichern können ( Beispielanwendungsfall ). Sie können weiterhin die 64-Bit-Adressgröße verwenden, es sei denn, Sie haben eine negative 32-Bit-Ganzzahl mit der Erweiterung Null in einem Register (dies wäre in diesem Fall ein großer vorzeichenloser Wert
[rdi + rdx]
).Beachten Sie, dass
push rsp
/pop rax
2 Bytes entsprichtmov rax,rsp
, sodass Sie weiterhin vollständige 64-Bit-Register in 2 Bytes kopieren können .quelle
ret 16
. Sie geben die Absenderadresse nicht an, verschieben ein Array und dannpush rcx
/ret
. Der Aufrufer müsste die Array-Größe kennen oder RSP irgendwo außerhalb des Stapels gespeichert haben, um sich selbst zu finden.Verwenden Sie spezielle Kurzformkodierungen für AL / AX / EAX sowie andere Kurzformen und Einzelbyte-Anweisungen
Bei den Beispielen wird der 32/64-Bit-Modus angenommen, bei dem die Standardoperandengröße 32 Bit beträgt. Ein Präfix mit Operandengröße ändert den Befehl in AX anstelle von EAX (oder umgekehrt im 16-Bit-Modus).
inc/dec
ein Register (außer 8-Bit):inc eax
/dec ebp
. (Nicht x86-64: Die0x4x
Opcode-Bytes wurden als REX-Präfixe verwendet. Diesinc r/m32
ist die einzige Codierung.)8-Bit -
inc bl
2 Byte, unter Verwendung desinc r/m8
opcode + ModR / M - Operanden kodieren . So verwendeninc ebx
zu erhöhenbl
, wenn es sicher ist. (zB wenn Sie das ZF-Ergebnis nicht benötigen, wenn die oberen Bytes möglicherweise nicht Null sind).scasd
:e/rdi+=4
, erfordert, dass das Register auf einen lesbaren Speicher zeigt. Manchmal nützlich, auch wenn Sie sich nicht für das FLAGS-Ergebnis interessieren (wiecmp eax,[rdi]
/rdi+=4
). Und im 64-Bit-Modusscasb
kann als 1-Byte arbeiteninc rdi
, wenn lodsb oder stosb nicht nützlich sind.xchg eax, r32
: Hier wird von 0x90 NOP kam:xchg eax,eax
. Beispiel: 3 Register mit zweixchg
Befehlen in einercdq
/idiv
-Schleife für GCD in 8 Bytes neu anordnen, wobei die meisten Befehle Einzelbytes sind, einschließlich eines Missbrauchs voninc ecx
/loop
anstelle vontest ecx,ecx
/jnz
cdq
: Vorzeichenerweiterung von EAX in EDX: EAX, dh Kopieren des hohen EAX-Bits in alle EDX-Bits. Um eine Null mit bekannten nicht-negativen Werten zu erstellen, oder um eine 0 / -1 zu erhalten, mit der / sub oder maskiert wird. x86-Geschichtsstunde:cltq
vs.movslq
, und auch AT & T vs. Intel-Mnemonics für diese und die verwandtencdqe
.lodsb / d : like
mov eax, [rsi]
/rsi += 4
without clobbering flags. (Angenommen, DF ist klar, welche Standardaufrufkonventionen für die Funktionseingabe erforderlich sind.) Außerdem stosb / d, manchmal scas und seltener movs / cmps.push
/pop reg
. ZB im 64-Bit-Modus istpush rsp
/pop rdi
2 Byte,mov rdi, rsp
benötigt aber ein REX-Präfix und ist 3 Byte.xlatb
existiert, ist aber selten nützlich. Eine große Nachschlagetabelle sollte vermieden werden. Ich habe auch noch nie eine Verwendung für AAA / DAA oder andere gepackte BCD- oder 2-ASCII-Ziffern-Anweisungen gefunden.1 Byte
lahf
/sahf
sind selten nützlich. Sie könntenlahf
/and ah, 1
als Alternative zusetc ah
, aber es ist in der Regel nicht nützlich.Und speziell für CF gibt
sbb eax,eax
es eine 0 / -1 oder sogar eine nicht dokumentierte, aber universell unterstützte 1-Byte-Größesalc
(setze AL von Carry), die effektiv keinesbb al,al
Auswirkung auf Flags hat. (In x86-64 entfernt). Ich habe SALC in der User Appreciation Challenge # 1 verwendet: Dennis ♦ .1-Byte
cmc
/clc
/stc
(Flip ("Komplement"), Clear oder Set CF) sind selten nützlich, obwohl ich eine Verwendung für einecmc
Addition mit erweiterter Genauigkeit mit Basis 10 ^ 9-Chunks gefunden habe. Um CF bedingungslos zu setzen / löschen, lassen Sie dies normalerweise als Teil eines anderen Befehls geschehen, z. B.xor eax,eax
CF und EAX löschen. Es gibt keine entsprechenden Anweisungen für andere Bedingungsflags, nur DF (Zeichenfolgenrichtung) und IF (Interrupts). Das Carry Flag ist speziell für viele Anweisungen. Shifts setzen es,adc al, 0
können es in 2 Byte zu AL hinzufügen, und ich erwähnte zuvor die undokumentierte SALC.std
/cld
Scheinen selten wert . Insbesondere im 32-Bit-Code ist es besser, nurdec
einen Zeiger und einenmov
oder einen Speicherquellenoperanden für einen ALU-Befehl zu verwenden, anstatt DF so zu setzenlodsb
/stosb
nach unten statt nach oben zu gehen. Normalerweise , wenn Sie nach unten überhaupt brauchen, haben Sie noch einen anderen Zeiger geht nach oben, so dass Sie mehr brauchen würden als einestd
undcld
in der gesamten Funktion Verwendunglods
/stos
für beide. Verwenden Sie stattdessen einfach die Zeichenfolgenanweisungen für die Aufwärtsrichtung. (Die Standardaufrufkonventionen garantieren DF = 0 bei der Funktionseingabe, sodass Sie davon ausgehen können, dass dies ohne Verwendung von kostenlos istcld
.)8086 history: Warum gibt es diese Kodierungen?
Im Original 8086 war AX ganz Besonderes: Anweisungen wie
lodsb
/stosb
,cbw
,mul
/div
und andere implizit verwenden. Das ist natürlich immer noch der Fall; Der aktuelle x86 hat keinen der 8086-Opcodes gelöscht (zumindest keinen der offiziell dokumentierten). Spätere CPUs fügten neue Anweisungen hinzu, die bessere / effizientere Möglichkeiten boten, Dinge zu erledigen, ohne sie zuerst in AX zu kopieren oder zu tauschen. (Oder zu EAX im 32-Bit-Modus.)Zum Beispiel fehlten bei 8086 spätere Zusätze wie
movsx
/movzx
zum Laden oder Verschieben + Vorzeichen-Erweitern oder 2- und 3-Operandenimul cx, bx, 1234
, die kein High-Half-Ergebnis liefern und keine impliziten Operanden haben.Auch 8086 Haupt Engpass war Befehl holen, so die Optimierung für die Code-Größe wichtig war für die Leistung damals . Der ISA-Designer von 8086 (Stephen Morse) hat viel Opcode -Code für Sonderfälle für AX / AL ausgegeben, einschließlich spezieller (E) AX / AL-Ziel-Opcodes für alle grundlegenden ALU-Anweisungen von src , nur opcode + instant ohne ModR / M-Byte. 2 Byte
add/sub/and/or/xor/cmp/test/... AL,imm8
oderAX,imm16
oder (im 32-Bit-Modus)EAX,imm32
.Es gibt jedoch keinen Sonderfall für
EAX,imm8
, sodass die reguläre ModR / M-Codierungadd eax,4
kürzer ist.Es wird davon ausgegangen, dass Sie einige Daten in AX / AL bearbeiten möchten. Daher sollten Sie ein Register mit AX tauschen, vielleicht sogar öfter, als ein Register mit AX zu kopieren
mov
.Alles, was mit der 8086-Befehlskodierung zu tun hat, unterstützt dieses Paradigma, angefangen von Befehlen
lodsb/w
über alle Sonderfallkodierungen für Direktbefehle mit EAX bis hin zur impliziten Verwendung auch für Multiplikationen / Divisionen.Lass dich nicht mitreißen; Es ist nicht automatisch ein Gewinn, alles zu EAX zu tauschen, besonders wenn Sie Sofort mit 32-Bit-Registern anstelle von 8-Bit verwenden müssen. Oder wenn Sie Operationen mit mehreren Variablen in Registern gleichzeitig verschachteln müssen. Oder wenn Sie Anweisungen mit 2 Registern verwenden, nicht sofort.
Aber denken Sie immer daran: Tue ich irgendetwas, das in EAX / AL kürzer wäre? Kann ich neu anordnen, damit ich dies in AL habe, oder nutze ich derzeit AL besser mit dem, wofür ich es bereits benutze?
Mischen Sie 8-Bit- und 32-Bit-Operationen frei, um die Vorteile zu nutzen, wann immer dies sicher ist (Sie müssen nicht in das vollständige Register oder was auch immer übertragen).
quelle
cdq
ist nützlich fürdiv
die Bedürfnisseedx
in vielen Fällen auf Null gesetzt .cdq
bevor Sie unsigniert sind,div
wenn Sie wissen, dass Ihre Dividende unter 2 ^ 31 liegt (dh nicht negativ, wenn Sie als signiert behandelt werden), oder wenn Sie sie verwenden, bevor Sieeax
einen potenziell großen Wert festlegen. Normalerweise (außerhalb von Code-Golf) würden Sie verwendencdq
als Setup füridiv
undxor edx,edx
vordiv
Verwenden Sie
fastcall
KonventionenDie x86-Plattform kennt viele Aufrufkonventionen . Sie sollten diejenigen verwenden, die Parameter in Registern übergeben. Auf x86_64 werden die ersten Parameter ohnehin in Registern übergeben, also kein Problem. Auf 32-Bit-Plattformen
cdecl
übergibt die Standardaufrufkonvention ( ) Parameter im Stapel, was für das Golfen ungeeignet ist - für den Zugriff auf Parameter im Stapel sind lange Anweisungen erforderlich.Bei Verwendung
fastcall
auf 32-Bit-Plattformen werden in der Regel 2 erste Parameter inecx
und übergebenedx
. Wenn Ihre Funktion 3 Parameter hat, können Sie sie auf einer 64-Bit-Plattform implementieren.C-Funktionsprototypen für
fastcall
Konventionen (entnommen aus dieser Beispielantwort ):quelle
Subtrahiere -128 anstatt 128 zu addieren
Addiere -128 anstatt 128 zu subtrahieren
quelle
< 128
in<= 127
der Größe einer unmittelbaren Operanden zu reduzierencmp
, oder gcc immer bevorzugt Neuanordnung vergleicht, um die Größe zu verringern, selbst wenn es nicht -129 gegen -128 ist.Erstelle 3 Nullen mit
mul
(danninc
/dec
um +1 / -1 sowie Null zu bekommen)Sie können eax und edx auf Null setzen, indem Sie in einem dritten Register mit Null multiplizieren.
Dies führt dazu, dass EAX, EDX und EBX in nur vier Bytes Null sind. Sie können EAX und EDX in drei Bytes auf Null setzen:
Von diesem Ausgangspunkt aus können Sie jedoch kein Register mit der dritten Null in einem weiteren Byte oder ein Register mit +1 oder -1 in weiteren 2 Bytes erhalten. Verwenden Sie stattdessen die Mul-Technik.
Anwendungsbeispiel: Verketten der Fibonacci-Zahlen in Binärform .
Beachten Sie, dass
LOOP
ECX nach Beendigung einer Schleife Null ist und zum Nullen von EDX und EAX verwendet werden kann. Sie müssen nicht immer die erste Null mit erstellenxor
.quelle
CPU-Register und Flags befinden sich in bekannten Startzuständen
Wir können davon ausgehen, dass sich die CPU in einem bekannten und dokumentierten Standardzustand befindet, der auf der Plattform und dem Betriebssystem basiert.
Beispielsweise:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
quelle
_start
. Also ja, es ist fair, dies auszunutzen, wenn Sie ein Programm anstelle einer Funktion schreiben. Ich habe das in Extreme Fibonacci gemacht . (In einer dynamisch verknüpften ausführbaren Datei wird ld.so ausgeführt, bevor zu Ihrer Datei gesprungen wird_start
, und es verbleibt kein Speicherplatz in den Registern, aber statisch ist nur Ihr Code.)Verwenden Sie zum Addieren oder Subtrahieren von 1 das eine Byte
inc
oder diedec
Anweisungen, die kleiner sind als die Anweisungen zum Addieren und Subtrahieren von Mehrbytes.quelle
inc/dec r32
wobei die Registernummer im Opcode codiert ist. Istinc ebx
also 1 Byte, ist aberinc bl
2. Noch kleiner alsadd bl, 1
natürlich für andere Register alsal
. Beachten Sie auch, dassinc
/dec
CF unverändert bleibt, aber aktualisieren Sie die anderen Flags.lea
für MatheDies ist wahrscheinlich eines der ersten Dinge, die man über x86 erfährt, aber ich lasse es hier als Erinnerung.
lea
kann verwendet werden, um eine Multiplikation mit 2, 3, 4, 5, 8 oder 9 durchzuführen und einen Versatz hinzuzufügen.So berechnen Sie beispielsweise
ebx = 9*eax + 3
in einem Befehl (im 32-Bit-Modus):Hier ist es ohne Versatz:
Wow!
lea
Kann natürlich auch verwendet werden, um mathematische Aufgabenebx = edx + 8*eax + 3
zur Berechnung der Array-Indizierung auszuführen.quelle
lea eax, [rcx + 13]
die Version ohne zusätzliche Präfixe für den 64-Bit-Modus ist. 32-Bit-Operandengröße (für das Ergebnis) und 64-Bit-Adressgröße (für die Eingaben).Die Schleifen- und Zeichenkettenbefehle sind kleiner als alternative Befehlssequenzen. Am nützlichsten ist,
loop <label>
welche kleiner als die beiden Befehlssequenzendec ECX
undjnz <label>
undlodsb
kleiner alsmov al,[esi]
und istinc si
.quelle
mov
small wird bei Bedarf sofort in die unteren Register verschobenWenn Sie bereits wissen, dass die oberen Bits eines Registers 0 sind, können Sie einen kürzeren Befehl verwenden, um ein unmittelbares Bit in die unteren Register zu verschieben.
gegen
Verwenden Sie
push
/pop
für imm8, um die oberen Bits auf Null zu setzenDank an Peter Cordes.
xor
/mov
ist 4 Bytes, aberpush
/pop
ist nur 3!quelle
mov al, 0xa
ist gut, wenn Sie es nicht null-erweitert auf die volle Ausrichtung brauchen. Wenn Sie dies jedoch tun, ist xor / mov 4 Byte im Vergleich zu 3 Byte für push imm8 / pop oderlea
von einer anderen bekannten Konstante. Dies kann in Kombination mitmul
der Nullstellung von 3 Registern in 4 Bytes nützlich sein , odercdq
wenn Sie viele Konstanten benötigen.[0x80..0xFF]
, die nicht als vorzeichenerweitertes imm8 darstellbar sind. Oder wenn Sie die oberen Bytes bereits kennen, z. B.mov cl, 0x10
nach einerloop
Anweisung, weil der einzige Wegloop
, nicht zu springen, der ist, wenn es gemacht wirdrcx=0
. (Ich denke, Sie haben das gesagt , aber Ihr Beispiel verwendet einxor
). Sie können sogar das Low-Byte eines Registers für etwas anderes verwenden, sofern es durch etwas anderes auf Null (oder was auch immer) zurückgesetzt wird, wenn Sie fertig sind. zB mein Fibonacci-Programm bleibt-1024
in ebx und benutzt bl.xchg eax, r32
) zBmov bl, 10
/dec bl
/jnz
so Ihr Code nicht über das hohe Bytes RBX schert.Die FLAGS werden nach vielen Anweisungen gesetzt
Nach vielen arithmetischen Anweisungen werden das Carry Flag (ohne Vorzeichen) und das Overflow Flag (mit Vorzeichen) automatisch gesetzt ( weitere Informationen ). Das Vorzeichen-Flag und das Null-Flag werden nach vielen arithmetischen und logischen Operationen gesetzt. Dies kann zur bedingten Verzweigung verwendet werden.
Beispiel:
ZF wird durch diese Anweisung gesetzt, sodass wir es zur bedingten Verzweigung verwenden können.
quelle
test al,1
; das bekommst du normalerweise nicht umsonst. (Oderand al,1
eine ganze Zahl 0/1 in Abhängigkeit von ungeraden / geraden zu erstellen.)test
/ zu vermeidencmp
", dann wäre das ein ziemlich einfacher x86-Anfänger, aber dennoch eine Aufwertung wert.Verwenden Sie do-while-Schleifen anstelle von while-Schleifen
Dies ist nicht x86-spezifisch, aber ein allgemein verwendbarer Einsteigertipp. Wenn Sie wissen, dass eine while-Schleife mindestens einmal ausgeführt wird, speichert das Umschreiben der Schleife als do-while-Schleife mit der Prüfung der Schleifenbedingung am Ende häufig einen 2-Byte-Sprungbefehl. In besonderen Fällen können Sie sogar verwenden
loop
.quelle
do{}while()
die natürliche Loop-Sprache bei der Montage verwendet wird (insbesondere aus Gründen der Effizienz). Beachten Sie auch, dass 2-Bytejecxz
/jrcxz
vor einer Schleife sehr gut funktioniertloop
, um den Fall "muss null Mal ausgeführt werden" "effizient" zu behandeln (auf den seltenen CPUs, bei denenloop
es nicht langsam ist).jecxz
ist auch verwendbar innerhalb der Schleife einen zu implementierenwhile(ecx){}
, mitjmp
dem Boden bei.Verwenden Sie die gewünschten Aufrufkonventionen
System V x86 verwendet den Stack und System V x86-64 Anwendungen
rdi
,rsi
,rdx
,rcx
usw. für Eingabeparameter undrax
als Rückgabewert, aber es ist durchaus sinnvoll Ihre eigene Aufrufkonvention zu verwenden. __fastcall verwendetecx
undedx
als Eingabeparameter, und andere Compiler / Betriebssysteme verwenden ihre eigenen Konventionen . Verwenden Sie den Stapel und alle Register als Ein- / Ausgabe, wenn Sie dies möchten.Beispiel: Der repetitive Bytezähler unter Verwendung einer cleveren Aufrufkonvention für eine 1-Byte-Lösung.
Meta: Schreiben Eingang zu den Registern , Schreiben Ausgang Register
Weitere Quellen: Anmerkungen von Agner Fog zu Anrufkonventionen
quelle
int 0x80
der, der ein paar Einstellungen erfordert.int 0x80
in 32-Bit-Code odersyscall
in 64-Bit-Code aufzurufensys_write
, ist der einzige gute Weg. Das habe ich für Extreme Fibonacci verwendet . In 64-Bit-Code__NR_write = 1 = STDOUT_FILENO
, so können Siemov eax, edi
. Oder wenn die oberen Bytes von EAX Null sind,mov al, 4
im 32-Bit-Code. Du könntest auchcall printf
oderputs
, denke ich, eine "x86 asm for Linux + glibc" Antwort schreiben. Ich halte es für vernünftig, den PLT- oder GOT-Eintragsbereich oder den Bibliothekscode selbst nicht zu zählen.char*buf
und die Zeichenfolge mit manueller Formatierung erzeugt. zB so (umständlich auf Geschwindigkeit optimiert) wie FizzBuzz , wo ich String-Daten ins Register bekam und sie dann mitspeichertemov
, weil die Strings kurz und von fester Länge waren.Verwende bedingte Züge
CMOVcc
und MengenSETcc
Dies ist eher eine Erinnerung an mich selbst, aber auf den Prozessoren P6 (Pentium Pro) oder neuer gibt es Anweisungen für bedingte Sätze und Anweisungen für bedingte Verschiebungen. Es gibt viele Anweisungen, die auf einem oder mehreren in EFLAGS gesetzten Flags basieren.
quelle
cmov
einen 2-Byte-Opcode (0F 4x +ModR/M
) enthält, also mindestens 3 Byte. Die Quelle ist jedoch r / m32, sodass Sie bedingt 3 Bytes laden können. Anders als Verzweigungsetcc
ist in mehr Fällen als nützlichcmovcc
. Betrachten Sie dennoch den gesamten Befehlssatz und nicht nur die Basisanweisungen. (Obwohl SSE2- und BMI / BMI2-Befehle so umfangreich sind, dass sie selten nützlich sind. Sierorx eax, ecx, 32
sind 6 Byte lang und länger als mov + ror. Gut für die Leistung, nicht für Golf, es sei denn, POPCNT oder PDEP speichern viele isns.)setcc
.Sparen Sie
jmp
Bytes, indem Sie in if / then statt if / then / else anordnenDies ist sicherlich sehr einfach, dachte nur, ich würde dies als etwas zu denken, wenn Sie Golf spielen. Betrachten Sie als Beispiel den folgenden einfachen Code zum Dekodieren eines hexadezimalen Ziffernzeichens:
Dies kann um zwei Bytes verkürzt werden, indem ein "then" -Fall in einen "else" -Fall umgewandelt wird:
quelle
sub
Latenz auf dem kritischen Pfad für einen Fall nicht Teil einer schleifenbasierten Abhängigkeitskette ist (wie hier, wo jede Eingabeziffer unabhängig ist, bis 4-Bit-Blöcke zusammengeführt werden ). Aber ich denke trotzdem +1. Übrigens hat Ihr Beispiel eine separate Fehloptimierung: Wenn Siemovzx
am Ende ohnehin eine benötigen, verwenden Siesub $imm, %al
nicht EAX, um die 2-Byte-Codierung von no-modrm zu nutzenop $imm, %al
.cmp
indem Sie tunsub $'A'-10, %al
;jae .was_alpha
;add $('A'-10)-'0'
. (Ich glaube, ich habe die Logik richtig verstanden). Beachten Sie,'A'-10 > '9'
dass es keine Mehrdeutigkeiten gibt. Wenn Sie die Korrektur für einen Buchstaben subtrahieren, wird eine Dezimalstelle umgebrochen. Das ist also sicher, wenn wir davon ausgehen, dass unsere Eingabe ein gültiges Hex ist, genau wie Ihre.Sie können sequentielle Objekte aus dem Stapel abrufen, indem Sie esi auf esp setzen und eine Sequenz von lodsd / xchg reg, eax ausführen.
quelle
pop eax
/pop edx
/ ...? Wenn Sie sie auf dem Stapel belassen müssen, können Siepush
sie alle zurücksetzen, um ESP wiederherzustellen, und zwar immer noch 2 Bytes pro Objekt, ohne dass dies erforderlich istmov esi,esp
. Oder meinten Sie für 4-Byte-Objekte im 64-Bit-Code, wopop
8 Bytes erhalten würden? Übrigens können Sie sogarpop
eine Schleife über einen Puffer mit einer besseren Leistung alslodsd
z. B. für eine Addition mit erweiterter Genauigkeit in Extreme FibonacciFür Codegolf und ASM: Verwenden Sie Anweisungen, verwenden Sie nur Register, drücken Sie Pop, minimieren Sie den Registerspeicher oder speichern Sie sofort
quelle
Verwenden Sie zum Kopieren eines 64-Bit-Registers
push rcx
;pop rdx
anstelle eines 3-Bytemov
.Die Standardoperandengröße für Push / Pop ist 64-Bit, ohne dass ein REX-Präfix erforderlich ist.
(Ein Präfix mit Operandengröße kann die Push / Pop-Größe auf 16-Bit überschreiben, aber die 32-Bit-Push / Pop-Operandengröße kann im 64-Bit-Modus auch mit REX.W = 0 nicht codiert werden .)
Wenn eines oder beide Register
r8
.. sindr15
, verwenden Sie,mov
da Push und / oder Pop ein REX-Präfix benötigen. Schlimmstenfalls verliert dies tatsächlich, wenn beide REX-Präfixe benötigen. Offensichtlich sollten Sie im Codegolf normalerweise ohnehin r8..r15 meiden.Sie können Ihre Quelle während der Entwicklung mit diesem NASM-Makro besser lesbar halten . Denken Sie daran, dass die 8 Bytes unterhalb von RSP angezeigt werden. (In der roten Zone in x86-64 System V). Aber unter normalen Bedingungen ist es ein Ersatz für 64-Bit
mov r64,r64
odermov r64, -128..127
Beispiele:
Der
xchg
Teil des Beispiels ist, weil Sie manchmal einen Wert in EAX oder RAX erhalten müssen und es nicht wichtig ist, die alte Kopie beizubehalten. push / pop hilft dir aber nicht beim tauschen.quelle