2012-11-29 2 views
5

Ich versuche, Inline-x86-64-Assembly für GCC zu schreiben, um den MULQ-Befehl effizient zu verwenden. MULQ multipliziert das 64-Bit-Register RAX mit einem anderen 64-Bit-Wert. Der andere Wert kann ein beliebiges 64-Bit-Register (selbst RAX) oder ein Wert im Speicher sein. MULQ setzt die hohen 64 Bits des Produkts in RDX und die niedrigen 64 Bits in RAX.Kann GCC verschiedene Befehls-Mnemonik ausgeben, wenn zwischen mehreren alternativen Operandenbedingungen der Inline-Assemblierung gewählt wird?

Jetzt ist es einfach genug, um eine korrekte MULQ als Inline-Montage zum Ausdruck bringen:

#include <stdint.h> 
static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) 
{ 
    asm ("mulq %[y]" 
      : "=d" (*high), "=a" (*low) 
      : "a" (x), [y] "rm" (y)  
     ); 
} 

Dieser Code ist richtig, aber nicht optimal. MULQ ist kommutativ, also wenn y zufällig bereits in RAX ist, dann wäre es korrekt, y zu verlassen, wo es ist und die Multiplikation zu machen. Aber GCC weiß das nicht, daher wird es zusätzliche Anweisungen ausgeben, um die Operanden an ihre vordefinierten Orte zu verschieben. Ich möchte GCC sagen, dass es beide Eingaben an jedem Ort vornehmen kann, solange man in RAX landet und der MULQ auf den anderen Ort verweist. GCC hat dafür eine Syntax namens "multiple alternative constraints". Beachten Sie die Kommas (aber die Gesamt asm() ist gebrochen, siehe unten):

asm ("mulq %[y]" 
     : "=d,d" (*high), "=a,a" (*low) 
     : "a,rm" (x), [y] "rm,a" (y)  
    ); 

Leider ist dies nicht stimmt. Wenn GCC die zweite alternative Einschränkung auswählt, wird "mulq% rax" ausgegeben. Um klar sein, sollten Sie diese Funktion:

uint64_t f() 
{ 
    uint64_t high, low; 
    uint64_t rax; 
    asm("or %0,%0": "=a" (rax)); 
    mulq(&high, &low, 7, rax); 
    return high; 
} 

Zusammengestellt mit gcc -O3 -c -fkeep-inline-functions mulq.c sendet GCC diese Versammlung:

0000000000000010 <f>: 
    10: or  %rax,%rax 
    13: mov $0x7,%edx 
    18: mul %rax 
    1b: mov %rdx,%rax 
    1e: retq 

Die "mul% rax" sollte "mul% RDX" sein.

Wie kann diese Inline-Asm umgeschrieben werden, so dass in jedem Fall die richtige Ausgabe generiert wird?

+0

Eine Problemumgehung ist, ein Assembler-Makro in die Inline-Assembly zu setzen, etwa 'MULQ_FIX_OPERANDS (% 2,% 3)', das den Nicht-RAX-Operanden auswählt, aber das sieht wirklich hässlich aus. Ich hoffe, jemand hat eine bessere Lösung. – staufk

+0

GCC unterstützt die Einschränkung "%", um kommutative Operanden zwischen alternativen Bedingungen auszudrücken, aber das löst das Problem nicht, da MULQ nur den Operanden verwendet. Wenn nur "Gas" dachte "MULQ RAX, r/m64" war ein gültiges Format für diese Anweisung! – staufk

Antwort

2
__asm__ ("mulq %3" : "=a,a" (*low), "=d,d" (*high) : "%0,0" (x), "r,m" (y)) 

Dies ist ähnlich zu dem, was Sie in longlong.h enthalten mit verschiedenen GNU-Pakete zu finden; "r,m" eher als "rm" ist wirklich für clangs Vorteil. Die Mehrfachbeschränkungssyntax scheint immer noch wichtig für den Clang zu sein, wie es diskutiert wurde here. Was ist eine Schande, aber ich finde immer noch, dass claning Constraint-Matching (besonders bei x86 [-86]) schlechter abschneidet als gcc. für gcc:

__asm__ ("mulq %3" : "=a" (*low), "=d" (*high) : "%0" (x), "rm" (y)) 

würde ausreichen, und begünstigen (y) halten in einem Register würde, es sei denn, der Registerdruck zu hoch war; aber clang immer scheint in vielen Fällen zu verschütten. Meine Tests zeigen, dass es die erste Option "r" in der Mehrfach-Constraint-Syntax wählt.

"%3" als Multiplikand im Anweisung ermöglicht, daß entweder ein Register (bevorzugt) oder eine Speicherstelle, wie sie in dem dritten Operanden aliased, relativ zu Null, was (y). "0" aliasiert den 'nullten' Operanden: (*low), der explizit "a" ist, d.h. %rax für 64-Bit. Das führende Zeichen % in "%0" ist der kommutative Operator: Das heißt, (x) kann mit (y) pendeln, wenn dies die Registerzuweisung unterstützt. Offensichtlich ist mulq kommutativ als: x * y == y * x.

Wir sind eigentlich ziemlich eingeschränkt hier. mulq multipliziert den 64-Bit-Operanden %3 mit dem Wert in %rax, um das 128-Bit-Produkt zu erzeugen: %rdx:%rax. Die "0" (x) würde bedeuten, dass (x) in %rax geladen werden muss, und (y) in ein 64-Bit-Register oder Speicheradresse geladen werden muss. Jedoch bedeutet %0, dass (x), und der folgende Eingang (y) kann pendeln.

Ich würde auch auf die best practical inline assembly tutorial Ich habe gefunden. Während die gcc Referenzen 'autorisierend' sind, machen sie ein schlechtes Tutorial.


Dank Chris um die Auswahl der Fehler in meiner ursprünglichen Einschränkung Ordnung auf.

+0

Wenn ich versuche, Code, es berechnet y \ * y anstelle von x \ * y. – Chris

+0

Wenn ich 'uint64_t hi, lo, a = 20, b = 30; mulq (& hi, & lo, a, b); ', ich erhalte lo = 900. Die Asm, die ich bekomme, ist: '_mulq: pushq% rbp \\ movq% rsp,% rbp \\ movq% rdi, -8 (% rbp) \\ movq% rsi, -16 (% rbp) \\ movq% rdx, -24 (% rbp) \\ movq% rcx, -32 (% rbp) \\ movq -8 (% rbp),% rax \ movq -16 (% rbp),% rcx \\ movq -24 (% rbp),% rdx \ movq -32 (% rbp),% rsi \ movq% rax, -40 (% rbp) \\ ## InlineAsm Start \\ mulq% rsi \\ ## InlineAsm Ende \\ movq -40 (% rbp),% rsi \\ movq% rdx, (% rsi) \\ movq% rax, (% rcx) \\ popq% rbp \\ ret' Entschuldigung für das Chaos, aber ich bin neu inline asm, ich bin mir nicht sicher welche Information du willst. – Chris

+0

Ja, beide Linien funktionieren jetzt perfekt! :) (Ich habe mit gcc getestet) Es gibt nur einen kleinen Tippfehler: eine fehlende "nach% 3 im Code für gcc. Vielen Dank für diese großartige Antwort! – Chris

0

Verwenden Trick wie folgt aus:

void multiply(unsigned& rhi, unsigned& rlo, unsigned a, unsigned b) 
{ 
__asm__(
" mull %[b]\n" 
:"=d"(rhi),"=a"(rlo) 
:"1"(a),[b]"rm"(b)); 
} 

Hinweis "1" Argument-Spezifikation für Eingangsoperanden a. Dies bedeutet "setze 'a' an die gleiche Stelle, wo Argument # 1 ist".

0

Brett Hale answer erzeugt in einigen Fällen suboptimalen Code (mindestens auf GCC 5.4.0).

Gegeben:

static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) { 
    __asm__ ("mulq %3" : "=a" (*low), "=d" (*high) : "%0" (x), "rm" (y) : "cc"); 
} 

uint64_t foo(); 

kompiliert Dann mulq(&high, &low, foo(), 42) zu:

call foo 
    movl $42, %edx 
    mulq %rdx 

... was optimal ist.

Aber umgekehrt nun die Reihenfolge der Operanden:

mulq(&high, &low, 42, foo()); 

... und schauen, was passiert mit dem kompilierten Code:

call foo 
    movq %rax, %rdx 
    movl $42, %eax 
    mulq %rdx 

Hoppla! Was ist passiert? Der Compiler besteht darauf, 42 in rax zu setzen, und muss daher den Rückgabewert von foo() aus rax verschieben. Offensichtlich ist die % (kommutative) Operandeneinschränkung fehlerhaft.

Gibt es eine Möglichkeit, dies zu optimieren? Es stellt sich heraus, dass es da ist, obwohl es ein bisschen unordentlich ist.

static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) { 
    __asm__ (
     ".ifnc %2,%%rax\n\t" 
     "mulq %2\n\t" 
     ".else\n\t" 
     "mulq %3\n\t" 
     ".endif" 
     : "=a,a" (*low), "=d,d" (*high) 
     : "a,rm" (x), "rm,a" (y) 
     : "cc"); 
} 

Jetzt mulq(&high, &low, foo(), 42) kompiliert:

call foo 
    movl $42, %edx 
    .ifnc %rax,%rax 
    mulq %rax 
    .else 
    mulq %rdx 
    .endif 

Und mulq(&high, &low, 42, foo()) zu kompiliert:

call foo 
    movl $42, %edx 
    .ifnc %rdx,%rax 
    mulq %rdx 
    .else 
    mulq %rax 
    .endif 

Dieser Code verwendet einen Assembler Trick um die Beschränkung zu erhalten, die GCC uns nicht emittieren lassen Unterschiedlicher Assemblercode in Abhängigkeit von der gewählten Alternative.In jedem Fall gibt der Assembler nur eine der beiden möglichen Anweisungen mulq aus, je nachdem, ob der Compiler x oder y in rax gewählt hat.

Leider ist dieser Trick nicht optimal, wenn wir den Rückgabewert von foo() durch den Wert an einem Speicherplatz multiplizieren:

extern uint64_t bar; 

Jetzt mulq(&high, &low, bar, foo()) kompiliert:

call foo 
    .ifnc bar(%rip),%rax 
    mulq bar(%rip) 
    .else 
    mulq %rax 
    .endif 

..., die optimal ist , aber mulq(&high, &low, foo(), bar) kompiliert zu:

movq bar(%rip), %rbx 
    call foo 
    .ifnc %rax,%rax 
    mulq %rax 
    .else 
    mulq %rbx 
    .endif 

... die unnötigerweise bar in rbx kopiert.

Ich konnte leider keinen Weg finden, GCC-Ausgang optimalen Code in allen Fällen zu machen. Das Erzwingen, dass der Multiplikator ein Speicheroperand ist, veranlasst GCC lediglich, das bar(%rip) in ein Register zu laden und dann dieses Register in einem temporären Stapelspeicherort zu speichern, den es dann an mulq weiterleitet.