Win(BCB)C++File mit Assemblercode in QT/Unix Projekt einbauen
-
Hallo
ich fang erst mit QT an und möchte mein Windos Programm(Build 2008) mit QT auf LInux umbauen. Da ein C++ File viele Funktionen in Assembler hat, bekomme ich es auf QT mit GCC nicht zum Compilieren. Wie oder was muß in CMake oder im Kit ändern? Das, was ich vom Inline-ass gelesen habe kann ich offensichtlich nicht brauchen.void bgi::addieren(unsigned *a,unsigned *b,unsigned *z) { _asm { xor eax, eax // CF = 0 !! mov esi, a mov edi, b mov ebx, z movsx ecx, [ebx].Laenge // Z�hler init adduuu1:mov eax, esi[ecx*4-4] adc eax, edi[ecx*4-4] mov ebx[ecx*4-4], eax loop adduuu1 } }
-
@RudiMBM
Oh je, ich fürchte du musst den ganzen Assembler-Code überarbeiten, da sich die GNU Assembler Syntax etwas von der Intel Syntax unterscheidet.https://de.wikipedia.org/wiki/GNU_Assembler
Evt. hilft hier die
.intel_syntax noprefix
Anweisung.BTW: Gibt es eine Möglichkeit den Code in C++ zu schreiben?
-
@Quiche-Lorraine sagte in [Win(BCB)C++File mit Assemblercode in QT/Unix Projekt einbauen](/forum
BTW: Gibt es eine Möglichkeit den Code in C++ zu schreiben?
@RudiMBM Das würde ich auch empfehlen, allerdings verstehe ich, weshalb das in Assembler implementiert wurde: Wenn ich das richtig lese, handelt es sich um eine Big-integer-Addition(?), welche sich die
ADC
-Instruktion der x86-CPUs zunutze macht. Das ist eine Addition mit Übertrag und hat keine direkte Entsprechung in C++. Man ist also darauf angewiesen, dass der Compiler das gut optimiert.Wenn er das
ADC
hinbekommt, dann wäre eine C++-Version meiner Meinung nach zu bevorzugen. Der Assembler-Code wird nämlich meines Wissens so übernommen wie er da steht, während mit C++ Code der Compiler z.B. noch die Schleife vektorisieren und den Code möglicherweise besser inlinen könnte.Das müsste man allerdings mal austesten und sich genau ansehen, was für Code der Compiler erzeugt - und wenn die Performance wichtig ist, natürlich immer messen. Wenn ich später Zeit dazu finde, dann spiele ich damit mal etwas herum, das interessiert mich auch, was der Compiler aus einer C++-Implementierung macht.
Alternativ kann man das natürlich in GCC-kompatibles Inline-Assembler übertragen. Das ist ein bisschen komplizierter als die MSVC-
_asm
-Blöcke, da sollstest du dich vielleicht mal etwas einlesen (ich mach sowas auch nur selten und müsste das auch wieder tun).
-
Kann man den GCC nicht sogar insgesamt auf Intelsyntax umstellen mit dem
-masm
Parameter? Dann braucht man nicht jedes Stück Assembler mit.intel_syntax noprefix
zu versehen. Geht natürlich nicht, wenn irgendwo im Programm ein anderer Stil gemischt wird, aber das ist ja eher unwahrscheinlich. Habe ich aber noch nie benutzt, ich garantiere für nix.
-
@SeppJ sagte in Win(BCB)C++File mit Assemblercode in QT/Unix Projekt einbauen:
Dann braucht man nicht jedes Stück Assembler mit
.intel_syntax noprefix
zu versehen. Geht natürlich nicht, wenn irgendwo im Programm ein anderer Stil gemischt wird, aber das ist ja eher unwahrscheinlich. Habe ich aber noch nie benutzt, ich garantiere für nix.Es reicht einmal am Anfang des Inline-Assembler-Blocks und das würde ich auch gerade wegen dem "gemischten Stil" empfehlen (und weil diese gruselige AT&T-Sytax irgendwie Standard in der GCC-Welt ist). Dass sich der Code ohne eine eine spezielle Compiler-Option bauen lässt, erachte ich als Vorteil, der den Preis der extra Zeilen wert ist IMHO.
-
Danke an Alle. @Finnegan du hast es gleich erkannt. Das ist aber eine Ganze Bigint-Lib, allerdings nur Grundarten und spezielles und soll außerdem schnell sein. @Quiche-Lorraine, der Wikilink ist gut. Obwohl ich selbst auf Wiki helfe, habe ich nur nach dem GCC geschaut, aber da ist kein Link auf den Assembler. Den werde ich gleich einbauen.
Ich habe mir auch mit einem anderen Linux-Tool eine .obj Datei machen kann, die ich QT verwenden kann. Kenn ich aber nix.
-
Alternativ könnte man prüfen ob man nicht eine externe bigint library verwendet, statt eine eigene Implementierung.
Besonders, wenn man neben verschiedenen Betriebsystemen auch noch andere CPU architekturn (z.b. ARM) in zukunft unterstützen möchte.Zwei Beispiele die ich mit einer kurzen suche gefunden habe:
https://gmplib.org/
https://www.boost.org/doc/libs/1_84_0/libs/multiprecision/doc/html/index.html (Was wohl intern gmplib nutzt)
-
@RudiMBM Ich habe nochmal was rumexperimentiert:
#include <cstdint> #include <cassert> void add1( const std::uint32_t* a, const std::uint32_t* b, std::uint32_t* result, unsigned length ) { assert(length > 0); std::uint32_t carry = 0; for (unsigned i = length - 1; i >= 0; --i) { std::uint32_t value = a[i]; value += b[i] + carry; carry = value < a[i]; result[i] = value; } } void add2( const std::uint32_t* a, const std::uint32_t* b, std::uint32_t* result, unsigned length ) { assert(length > 0); std::uint32_t carry = 0; for (unsigned i = length - 1; i >= 0; --i) result[i] = __builtin_addc(a[i], b[i], carry, &carry); } void add3( const std::uint32_t* a, const std::uint32_t* b, std::uint32_t* result, unsigned length ) { assert(length > 0); asm volatile ( R"( .intel_syntax noprefix xor eax, eax 1: mov eax, dword ptr [esi + ecx * 4 - 4] adc eax, dword ptr [edi + ecx * 4 - 4] mov dword ptr [ebx + ecx * 4 - 4], eax loop 1 b )" : : "S" (a), "D" (b), "b" (result), "c" (length) : "memory", "cc" ); } void add4( const std::uint32_t* a, const std::uint32_t* b, std::uint32_t* result, unsigned length ) { assert(length > 0); asm volatile ( R"( .intel_syntax noprefix xor eax, eax 1: mov eax, dword ptr [edi + ecx * 4 - 4] adc eax, dword ptr [esi + ecx * 4 - 4] mov dword ptr [edx + ecx * 4 - 4], eax loop 1 b )" : : "D" (a), "S" (b), "d" (result), "c" (length) : "memory", "cc" ); }
GCC: https://godbolt.org/z/j17recbq5
Clang: https://godbolt.org/z/3nvaPdn6oadd1
ist eine naive C++-Implementierung. Nach meinem Verständnis sollte eigentlich der Ausdruckvalue < a[i]
nach der Addition genau danntrue
sein, wenn ein Überlauf stattgefunden hat, also dem Carry-Flag entsprechen. GCC und Clang scheinen das allerdings nicht zu erkennen, bzw. aus anderen Gründen hier keineADC
-Instruktion zu generieren. Das heisst aber nicht zwangsläufig, dass das schlechter Assembler-Code ist, das weiss man erst wenn man das gegen die Alternativen benchmarkt.add2
verwendet das GCC/Clang-spezifische Builtin__builtin_addc
das soweit ich das sehe derADC
-Instruktion entsprechen sollte. Hier generiert GCC dann auch eineADC
-Instruktion, Clang scheint das aber nicht für notwendig (oder keine gute Idee) zu halten. So ein Builtin hat natürlich genau wie die C++-Implementierung den Vorteil, dass es nicht CPU-spezifisch ist. Das wird auch auf ARM und anderen CPUs funktionieren, die entweder keinADC
kennen oder wo solche Additionen mit Übertrag anders gemacht werden können.add3
ist die GCC/Clang-spezifische Inline-Assember-Implementierung.volatile
hat hier den Effekt, dass die Assembler-Instruktionen "wie sie sind" übernommen und nicht vom Compiler weiter optimiert/umgeschrieben werden sollen. Die Inputs sind hier wie folgt definiert (der Compiler initialisiert die Register entsprechend):"S" (a)
: Wert (Adresse des Pointers) vona
inESI
(bzw.RSI
für 64-Bit Code)
"D" (b)
: Wert (Adresse des Pointers) vonb
inEDI
(bzw.RDI
für 64-Bit Code)
"b" (result)
: Wert (Adresse des Pointers) vonresult
inEBX
(bzw.RBX
für 64-Bit Code)
"c" (length)
: Wert vonlength
inECX
(bzw.RCX
für 64-Bit Code): "memory", "cc"
ist die Clobber-Liste, damit teilt man dem Compiler mit, was der Assembler-Code alles modifiziert. Das ist notwendig, damit der Compiler korrekten Code erzeugt (z.B. muss der Compiler eventuell ein Register sichern oder Daten aus dem Speicher neu laden, wenn die vom ASM-Code verändert wurden):memory
bedeutet, dass der Code Speicher veändert, nämlich den, auf denresult
zeigt.cc
bedeutet, dass das Flags-Register verändert wird (wegen der Addition).EAX
, sowie Register, die in der Input-Liste initialisiert werden, müssen hier nicht nochmal extra angegegeben werden.Ich sollte auch nochmal darauf Hinweisen, dass dieser Code genau wie deiner auch 32-Bit-spezifisch ist (!), da für die Speicheradressen die 32-Bit-Register
ESI
,EDI
undEBX
verwendet werden. Das mag zwar auf den ersten Blick auch mit 64-Bit funktionieren, aber nur dann, wenn die Speicheradressen unterhalb der 4GiB-Grenze liegen. Darüber würde der Code wahrscheinlich vor die Wand laufen, da die oberen 32 Bit der Adresse hier einfach abgeschnitten werden. Die 64-Bit-Varianten dieser Register wärenRSI
,RDI
undRBX
. Das müsste man im Code noch entsprechen handhaben, wenn man mit 64-Bit-Adressen arbeitet, bzw. eine Compiler-Weiche einbauen, wenn man auch noch 32-Bit unterstützen will. Das macht es alles nicht unkomplizierter und ist noch ein Grund mehr für eine nicht-ASM-Implementierung (C++ oder Builtin) oder eben eine andere BigInt-Bibliothek zu verwenden, welche diese Probleme alle für dich löst.add4
ist im Prinzip dasselbe wieadd3
allerdings habe ich hier den Assembler-Code so umgeschieben, dass er die Register verwendet, in denen die Funktionsargumente per x86 Calling Convention reinkommen. Das macht den Assembler-Code noch etwas kürzer, da die Register dann nicht mehr so umgetauscht werden müssen, dass sie auf den Code passen - sondern der Code passt direkt auf die Register.Einen Benchmark habe ich nicht gemacht und auch wenn der erzeugte Code von
add4
auf den ersten Blick sehr attraktiv kompakt aussieht, weiss man erst ob der wirklich was taugt, wenn man die Varianten gemessen hat.Auch sollte ich noch unterstreichen, dass ich meinen ganzen Code hier nicht auf Korrektheit geprüft habe. Es ist also gut möglich, dass hier irgendwo Blödsinn berechnet wird. Er ist aber nach "bestem Wissen und Gewissen runtergeschrieben" falls das was Wert ist .
Und nochwas: Bis auf
add1
kompilieren diese Lösungen natürlich alle nur mit GCC oder Clang undadd3
/add4
sind auch noch x86 only, sogar nur 32-Bit (s.o.). Also ordentlich Compiler-Weichen verwenden und alternative Implementierungen bereitstellen (z.B. deine MSVC-ASM-Lösung, wenn mit MSVC gebaut wird oder eine C++-Implementierung wenn z.B. auch mit MSVC für ARM kompiliert werden soll).
-
@Finnegan super, mensch da dast du dir aber arbeit gemacht. Da ich viele Ass-Teile habe ist es mir zu aufwendig. Ursprünglich wollte ich mal was schnelles haben und LIb mit variablen Zifferanzahlen, aber zugunsten der Handelbarkeit kann auf schnell verzichten. Wobei ich auch keinen Speedtest mit einer externen bigint library gemacht habe. Ich muß nur noch rausfinden, ob eine solche auch dynamische Variablenlängen zulässt. Außerdem könne ich mit QT/GCC dann auch ausser Linux/X64 evtl. noch andere Systeme zu compilien. (Falls ich überhaupt so weit komme. Das BCB/2008 auf Win7 Projekt war mein letztes größeres. Es hat sich in 15 Jahren soviel verändert, dass mir scheint, ich muß trotz 30Jahren Computerei mal wieder neu anfangen).
@Finnegan kann es sein, dass du spass am Programmieren hast? Vielleich auch bei meinen Projekt mitmachen? Kannst mir dann mal Mailen.