2015-12-14 9 views
5

Ich versuche, eine Funktion zu implementieren, die 32-Bit-Operand mit 256-Bit-Operand in ARM-Assembly auf ARM Cortex-a8 multipliziert. Das Problem ist, dass mir die Register ausgehen und ich keine Ahnung habe, wie ich hier die Anzahl der verwendeten Register reduzieren kann. Hier ist meine Funktion:ARM-Assembly: kann ein Register in der Klasse 'GENERAL_REGS' beim Neuladen von 'asm' nicht finden

typedef struct UN_256fe{ 

uint32_t uint32[8]; 

}UN_256fe; 

typedef struct UN_288bite{ 

uint32_t uint32[9]; 

}UN_288bite; 
void multiply32x256(uint32_t A, UN_256fe* B, UN_288bite* res){ 

asm (

     "umull   r3, r4, %9, %10;\n\t" 
     "mov   %0, r3;   \n\t"/*res->uint32[0] = r3*/ 
     "umull   r3, r5, %9, %11;\n\t" 
     "adds   r6, r3, r4;  \n\t"/*res->uint32[1] = r3 + r4*/ 
     "mov   %1, r6;   \n\t" 
     "umull   r3, r4, %9, %12;\n\t" 
     "adcs   r6, r5, r3;  \n\t" 
     "mov   %2, r6;   \n\t"/*res->uint32[2] = r6*/ 
     "umull   r3, r5, %9, %13;\n\t" 
     "adcs   r6, r3, r4;  \n\t" 
     "mov   %3, r6;   \n\t"/*res->uint32[3] = r6*/ 
     "umull   r3, r4, %9, %14;\n\t" 
     "adcs   r6, r3, r5;  \n\t" 
     "mov   %4, r6;   \n\t"/*res->uint32[4] = r6*/ 
     "umull   r3, r5, %9, %15;\n\t" 
     "adcs   r6, r3, r4;  \n\t" 
     "mov   %5, r6;   \n\t"/*res->uint32[5] = r6*/ 
     "umull   r3, r4, %9, %16;\n\t" 
     "adcs   r6, r3, r5;  \n\t" 
     "mov   %6, r6;   \n\t"/*res->uint32[6] = r6*/ 
     "umull   r3, r5, %9, %17;\n\t" 
     "adcs   r6, r3, r4;  \n\t" 
     "mov   %7, r6;   \n\t"/*res->uint32[7] = r6*/ 
     "adc   r6, r5, #0 ; \n\t" 
     "mov   %8, r6;   \n\t"/*res->uint32[8] = r6*/ 

     : "=r"(res->uint32[8]), "=r"(res->uint32[7]), "=r"(res->uint32[6]), "=r"(res->uint32[5]), "=r"(res->uint32[4]), 
      "=r"(res->uint32[3]), "=r"(res->uint32[2]), "=r"(res->uint32[1]), "=r"(res->uint32[0]) 
     : "r"(A), "r"(B->uint32[7]), "r"(B->uint32[6]), "r"(B->uint32[5]), 
      "r"(B->uint32[4]), "r"(B->uint32[3]), "r"(B->uint32[2]), "r"(B->uint32[1]), "r"(B->uint32[0]), "r"(temp) 
     : "r3", "r4", "r5", "r6", "cc", "memory"); 

} 

EDIT-1: ich meine clobber Liste aktualisiert, basierend auf dem ersten Kommentar, aber ich habe immer noch die gleichen Fehler

+0

Ihre asm-Anweisung hat ein größeres Problem. Sie müssen alle Register, die Sie explizit in der Anweisung asm angegeben haben, zur Clobber-Liste hinzufügen (die auch "cc" enthalten muss). Jene Clobbers plus alle Register, die benötigt werden, um die Eingabe- und Ausgabeoperanden zu halten (die auch als früher Clobber markiert werden müssen), bedeuten, dass Sie viel mehr Register verwenden als ARM. Sie haben das Problem nur noch schlimmer bei Ihrem letzten Versuch gemacht. –

+0

@RossRidge Gibt es eine Möglichkeit, dass ich eine andere Notation anstelle von "r" 'vor meinen Eingaben verwenden kann und die richtigen Ergebnisse bekomme? Ich meine etwas wie "g" oder "m" '? – A23149577

+0

Sie brauchen wirklich eine Schleife [mit Iteration Count 8] als was Sie tun. Überdenken Sie: Wie würden Sie es tun, wenn Ihr Eingabevektor 20.000 Elemente enthält? Sie müssten reg für skalare 'A' Wert, reg für' B' ptr, reg für 'res' ptr, reg für Iteration zählen, und was auch immer Sie regs tun müssen, umull et. al [wahrscheinlich noch 4-6] auf jeder Schleifeniteration, so ist insgesamt ~ 10. So wie es ist, haben Sie keine Regs mit einer Vektorgröße von 2-3, geschweige denn 8. Um Ihren Vektoralgorithmus gerade zu machen, wie wäre es mit der Kodierung eines C Fnc, der das tut [dient auch als Referenz für Sie asm fnc]. –

Antwort

0

Eine einfache Lösung ist diese aufzubrechen und don benutze 'Clobber' nicht. Deklarieren Sie die Variablen als 'tmp1' usw. Versuchen Sie, keine mov Anweisungen zu verwenden; Lassen Sie den Compiler dies tun, wenn es muss. Der Compiler wird einen Algorithmus verwenden, um den besten "Fluss" von Informationen herauszufinden. Wenn Sie 'clobber' verwenden, können Register nicht wiederverwendet werden. So wie es jetzt ist, laden Sie zuerst den gesamten Speicher, bevor der Assembler ausgeführt wird. Dies ist schlecht, da Speicher/CPU-ALU pipeliert werden soll.

void multiply32x256(uint32_t A, UN_256fe* B, UN_288bite* res) 
{ 

    uint32_t mulhi1, mullo1; 
    uint32_t mulhi2, mullo2; 
    uint32_t tmp; 

    asm("umull   %0, %1, %2, %3;\n\t" 
     : "=r" (mullo1), "=r" (mulhi1) 
     : "r"(A), "r"(B->uint32[7]) 
); 
    res->uint32[8] = mullo1; /* was 'mov %0, r3; */ 
    volatile asm("umull   %0, %1, %3, %4;\n\t" 
     "adds   %2, %5, %6;  \n\t"/*res->uint32[1] = r3 + r4*/ 
    : "=r" (mullo2), "=r" (mulhi2), "=r" (tmp) 
    : "r"(A), "r"(B->uint32[6]), "r" (mullo1), "r"(mulhi1) 
    : "cc" 
    ); 
    res->uint32[7] = tmp; /* was 'mov %1, r6; */ 
    /* ... etc */ 
} 

Der ganze Zweck der ist 'gcc Inline-Assembler' nicht Assembler direkt in einer 'C' Datei zu codieren. Es ist die Registerallokationslogik des Compilers AND zu verwenden, die nicht leicht in 'C' durchgeführt werden kann. Die Verwendung von Carry-Logik in Ihrem Fall.

Wenn Sie es nicht zu einer großen 'asm' Klausel machen, kann der Compiler die Lasten aus dem Speicher planen, da er neue Register benötigt. Es wird auch Ihre 'UMULL' ALU-Aktivität mit der Lade-/Speichereinheit pipettieren.

Sie sollten clobber nur verwenden, wenn eine Anweisung implizit ein bestimmtes Register überlagert. Sie können auch etwas wie,

register int *p1 asm ("r0"); 

verwenden und das als eine Ausgabe verwenden. Allerdings kenne ich keine ARM-Befehle wie diese, außer denen, die den Stack verändern könnten, und Ihr Code verwendet diese natürlich nicht.

GCC weiß, dass Speicher ändert, wenn es als ein Eingang/Ausgang aufgeführt ist, so dass Sie keinen Speicher Clobber benötigen. In der Tat ist es schädlich, da der Speicher Clobber ein compiler memory barrier ist, und dies verursacht, dass Speicher geschrieben wird, wenn der Compiler das für Letzteres planen könnte.


Die Moral ist Verwendung Gcc Inline-Assembler mit dem Compiler zu arbeiten. Wenn Sie in Assembler programmieren und Sie riesige Routinen haben, kann die Registerbenutzung komplex und verwirrend werden. Typische Assembler-Codierer behalten nur eine Sache in einem Register pro Routine, aber das ist nicht immer die beste Verwendung von Registern. Der Compiler wird die Daten auf eine ziemlich schlaue Art und Weise mischen, die schwer zu schlagen ist (und nicht sehr befriedigend ist, um IMO zu codieren), wenn die Codegröße größer wird.

Sie möchten vielleicht the GMP library betrachten, die viele Möglichkeiten hat, einige der gleichen Probleme effizient anzugehen, wie es aussieht, wie Ihr Code hat.

+0

Zunächst einmal vielen Dank für Ihre kurze Antwort. Ich habe eine Frage: Wenn ich die beste Leistung aus dieser Funktion herausholen möchte, ist es nicht besser, den gesamten Code in der Inline-Montage zu implementieren? Ich weiß, dass ich wie hier keine Register mehr haben könnte, aber ich dachte daran, meine Zeiger ('* B' und' * res') in zwei Register zu setzen und mit Hilfe der 'ldr'-Anweisung auf jedes Array 'uint32_t' zuzugreifen. Ich bin nicht sicher, ob es möglich ist, aber die Leistung ist wirklich wichtig für mich – A23149577

+0

Ja, Sie können die 'ldr' in Ihrem Code verwenden; und ich dachte darüber nach als Benchmark. Der Compiler hat huisistics und smarts, um die ALU- und Laden/Speichern ("ldr/str") Anweisungen zu verschachteln. Es kann sogar 'ldm/stm' und/oder' lrdd/strd' geben, abhängig von der Speicherreihenfolge und den verfügbaren Registern und der Klasse der CPU (Sie werden dies für einen A8 fest codieren, wo das Laden/Speichern nicht möglich ist Compiler zu wählen). Auf alle Fälle können Sie eine große Routine machen und das 'ldr' verwenden, weil Ihnen die ARM-Register ausgegangen sind. Ich denke jedoch, dass Sie viel entdecken werden, wenn Sie die Ausgabe teilen und betrachten. –

+1

Sie könnten überrascht sein, dass Ihr Algorithmus speichergebunden/dominant und nicht CPU-gebunden/dominant ist. Sicherlich lässt Ihre derzeitige große Routine diese Operation * pipeline * nicht passieren oder passiert nicht parallel (Leute sagen auch, dass der Speicher blockiert ist). Ich glaube, dass Sie den Compiler schlagen können, nachdem Sie gesehen haben, dass er ausgegeben wird. Es ist selten für mich, es zu schlagen (bei größeren Routinen), wenn ich nicht sehe, was es vor der Hand machen könnte. Derzeit haben Sie 9/25 ~ = 36% der Anweisungen als 'mov'-Anweisungen und haben nicht berücksichtigt, was der Compiler zum Laden/Speichern von Dingen tut. –

Verwandte Themen