Was tut dieser Assembly Code? (Anfängerfrage)
-
Da du so konsequent diese Frage von Swordfish, DirkB und anderen ignorierst, versuche ich noch einmal mein Glück: Du bist dir vollkommen bewusst, dass die Feinheiten der x64-Floating-Point-Programmierung dir absolut gar nichts helfen, wenn du einen anderen Prozessortyp als eigentliche Zielplattform hast? Dass du gerade deine Zeit damit vertrödelst Spanisch zu büffeln, wenn du demnächst eine Französischklausur schreiben musst?
Schlimmer noch: Allem nach zu beurteilen, was du bisher gesagt hast, geht es in der Prüfung doch wohl eher um fundamentales wie Kontrollfluss und grundlegende Algorithmen, wohingegen Floating Point Berechnungen erst recht nutzloses Spezialwissen sind. Du lernst spanische Sprichwörter, wenn es um französische Grammatik geht.
-
...um dir das nochmal deutlich zu machen. Wähle in dem Compiler-Explorer-Link, den ich gepostet hatte (https://godbolt.org/z/7cxXFW) einfach mal rechts einen anderen Compiler, z.B. "Power64 AT 12.0" und vergleiche den generierten Assemblercode. Du wirst sehen: sieht ganz anders aus. Eben Französisch <-> Spanisch. Oder sogar böhmische Dörfer?
-
Ja ihr habt recht. x64 Assembler ist in diesem Fall nicht der geeignete Einstieg bzw. etwas völlig anderes als das zu Lernende.
Dennoch, wenn ich mit etwas anfange, möchte ich es eigentlich auch hinbekommen.
Also eigentlich versuche ich jetzt das hier zu berechnen: (cos(((314.0 / 100.0) / 180.0) * i) * j)
-
@EinNutzer0 Dann kannst du auch gleich 314. / 18000. berechnen.
Oder du berechnest einfach atan(1)/45. Das wäre genauer.
-
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Ich glaub jetzt inzwischen, die float literals sind das Problem...
Hast du meinen Beitrag gelesen?
-
@hustbaer : ja, hatte ich, danke dafür. Ich werd's nachher nochmal versuchen. Sorry, falls es langsam nervig wird.
Etwas hab ich noch übersehen, denn x ist nach wie vor 0:
getx: .LFB0: # main.c:7: float w2 = w * (M_PI / 180.0); # main.c:8: x = (int) (r * cos(w2)); # main.c:9: return x; push %rcx movl $0x4048f5c3, %ecx movl $0x43340000, %edx movd %ecx, %xmm0 movd %edx, %xmm1 divsd %xmm0, %xmm1 cvtsi2sd %ebx, %xmm1 mulsd %xmm0, %xmm1 movsd %xmm1, %xmm0 call cos@PLT movsd %xmm0, %xmm1 cvtsi2sd %eax, %xmm0 mulsd %xmm0, %xmm1 cvttsd2si %xmm1, %eax pop %rcx ret
-
@EinNutzer0
Bitte ganz lesen wenn möglich. Falls du dich jetzt wunderst warum ich dir einige Fehler nicht gleich früher gesagt habe: ich musste da selbst ein paar Dinge nachgoogeln. Ich kann zwar "normalen" x86/amd64 Assembler mehr oder weniger flüssig lesen, aber bei dem ganzen FPU/SSE Zeugs bin ich überhaupt nicht fit und weiss so ziemlich gar keinen Befehl auswendig. Daher hab ich das nicht früher gesehen. Und daher auch nicht früher erwähnt.divsd %xmm0, %xmm1
cvtsi2sd %ebx, %xmm1Sofern ich die komische % Syntax richtig verstehe... und ohne jetzt auf "Details" einzugehen wie dass hier immer nur die untere Hälfte des XMM Registers als double verwendet wird...
divsd %xmm0, %xmm1
entspricht%xmm1 = %xmm1 / %xmm0
D.h. du berechnest schonmal180.0/PI
, nichtPI/180.0
.cvtsi2sd %ebx, %xmm1
entspricht%xmm1 = %ebx (als Integer)
D.h. du überschreibst damit das Ergebnis der Division.Das
movsd %xmm0, %xmm1 cvtsi2sd %eax, %xmm0 mulsd %xmm0, %xmm1
macht auch irgendwie keinen Sinn. Du kopierst erst
%xmm0
nach%xmm1
, dann überschreibst du%xmm0
mit dem Wert aus%eax
, und dann multiplizierst du%xmm0
und%xmm1
. Mal davon ausgehend dass das überhaupt irgendwie Sinn macht, und uns der Inhalt von%xmm0
danach egal ist, erreichst du das selbe mitcvtsi2sd %eax, %xmm1 mulsd %xmm0, %xmm1
Davon abgesehen...
...wenn du mit 32 Bit floats in den XMM Registern arbeitest, istdivsd
der falsche Befehl, denn der arbeitet mit doubles (=64 Bit). Du musst entwederdivss
verwenden oder halt mit 64 Bit floats (aka. doubles) arbeiten. Analog dazu die ganzen anderensd
SSE Befehle *Kleiner Tip noch: Wenn du Assembler-Code schreibst, schreib neben jede Zeile den Inhalt der veränderten Register. Ala
push %rcx movl $0x4048f5c3, %ecx ; %ecx = 3.14 (as IEEE float) movl $0x43340000, %edx ; %edx = 180 (as IEEE float) movd %ecx, %xmm0 ; %xmm0 = float[3.14, 0, 0, 0] movd %edx, %xmm1 ; %xmm1 = float[180, 0, 0, 0] divss %xmm0, %xmm1 ; %xmm1 = float[180/3.14, 0, 0, 0] ...
*:
DIVSD = DIVide Scalar Double precision
DIVSD = DIVide Scalar Single precision
MOVD = MOVe Double-wordBei x86 Assembler ist mit "word" ein 16-Bit Stück gemeint, daher double-word (den Begriff verwendet so kaum jemand) aka. DWORD (liest man häufig) = 32 Bit. Sobald es um Floats geht finden dann die Begriffe "single precision" und "double precision" Anwendung. Hier ist "single precision" ein 32 Bit Float (auch oft nur "float" genannt) und "double precision" ein 64 Bit Float (auch oft nur "double" genannt).
Das "D" in MOVD und DIVSD steht also zwar beide Male für das Wort "double", aber das "double" bezieht sich auf eine ander "Basis". Und daher passt das so nicht zusammen.
Und falls du eine amd64 Referenz suchst: https://www.felixcloutier.com/x86/
Ist in Intel-Syntax in der komischen % Syntax hab ich auf die schnelle nixe gefunden.
-
ps: Aufpassen falls du mit single-precision rechnest:
cos
hat nendouble
als Parameter. Die Variante mit single-precision heisstcosf
.
-
Danke, hab alles nach
double
umgestellt - unddivss
oderdivsd
endlich "richtigherum" verwendet:getx: .LFB0: # main.c:7: float w2 = w * (M_PI / 180.0); # main.c:8: x = (int) (r * cos(w2)); # main.c:9: return x; pushq %rcx cvtsi2sd %eax, %xmm3 movq $314, %rcx movq $100, %rdx cvtsi2sdq %rcx, %xmm0 cvtsi2sdq %rdx, %xmm1 divsd %xmm1, %xmm0 movq $180, %rdx cvtsi2sdq %rdx, %xmm1 divsd %xmm1, %xmm0 cvtsi2sd %ebx, %xmm1 mulsd %xmm1, %xmm0 call cos@PLT mulsd %xmm3, %xmm0 cvttsd2si %xmm0, %eax popq %rcx ret
Jetzt wird genau einmal das richtige Ergebnis ausgegeben.
Außerdem hab ich noch herausgefunden, dasscos
eax ändert...Also ich hab bestimmt mit
pushq %rcx
usw. etwas falsch gemacht. Aber ich kann mich auch nicht mehr lange damit aufhalten.
-
Jetzt gehts (Aufrufkonventionen eingehalten), aber nicht alle Ausgaben sind richtig:
pushq %rbp movq %rsp, %rbp movq $314, %rcx movq $100, %rdx cvtsi2sdq %rcx, %xmm0 cvtsi2sdq %rdx, %xmm1 divsd %xmm1, %xmm0 movq $180, %rdx cvtsi2sdq %rdx, %xmm1 divsd %xmm1, %xmm0 cvtsi2sd %esi, %xmm1 mulsd %xmm1, %xmm0 call cos@PLT cvtsi2sd %edi, %xmm1 mulsd %xmm1, %xmm0 cvttsd2si %xmm0, %eax #movl %eax, -8(%rbp) #movl -8(%rbp), %eax leave ret
$ ./a.out i=000 x=025 y=000 i=001 x=024 y=000 i=003 x=024 y=001 i=005 x=024 y=002 i=007 x=024 y=003 i=010 x=024 y=004 i=012 x=024 y=005 i=014 x=024 y=006 i=017 x=023 y=007 i=019 x=023 y=008 i=022 x=023 y=009 i=024 x=022 y=010 i=027 x=022 y=011 i=029 x=021 y=012 i=032 x=021 y=013 i=033 x=020 y=013 i=035 x=020 y=014 i=037 x=019 y=015 i=040 x=019 y=016 i=041 x=018 y=016 i=043 x=018 y=017 i=044 x=017 y=017 i=047 x=017 y=018 i=048 x=016 y=018 i=050 x=016 y=019 i=051 x=015 y=019 i=054 x=014 y=020 i=056 x=013 y=020 i=058 x=013 y=021 i=059 x=012 y=021 i=062 x=011 y=022 i=064 x=010 y=022 i=067 x=009 y=023 i=069 x=008 y=023 i=072 x=007 y=023 i=074 x=006 y=024 i=077 x=005 y=024 i=079 x=004 y=024 i=081 x=003 y=024 i=084 x=002 y=024 i=086 x=001 y=024 i=088 x=000 y=024 i=093 x=-01 y=024 i=095 x=-02 y=024 i=097 x=-03 y=024 i=100 x=-04 y=024 i=102 x=-05 y=024 i=104 x=-06 y=024 i=107 x=-07 y=023 i=109 x=-08 y=023 i=112 x=-09 y=023 i=114 x=-10 y=022 i=117 x=-11 y=022 i=119 x=-12 y=021 i=122 x=-13 y=021 i=123 x=-13 y=020 i=125 x=-14 y=020 i=127 x=-15 y=019 i=130 x=-16 y=019 i=131 x=-16 y=018 i=133 x=-17 y=018 i=134 x=-17 y=017 i=137 x=-18 y=017 i=138 x=-18 y=016 i=140 x=-276 y=016 i=141 x=-270 y=015 ...
Woran liegt das nun wieder?
-
Ok, noch schnell abschließend...
Da tritt ein integer overflow auf:
cvtsi2sd %esi, %xmm1
in esi steht der Winkel w (0 bis 720).
esi ist ein 32-bit Register.
cvtsi2sd
interpretiert ein 32-bit Register als Vorzeichen Byte (-128 bis 127).
man kann es mitmovl
umgehen:movl %esi, 16(%rcx) cvtsi2sd 16(%rcx), %xmm1 mulsd %xmm1, %xmm0
etwas unschön wegen der relativen Adressierung. Vielleicht andere Ideeen?
Edit: Außerdem, da
pushq
undmovq
muss es dannleaveq
heißen.
-
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
cvtsi2sd interpretiert ein 32-bit Register als Vorzeichen Byte (-128 bis 127).
Bitte was?
-
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
call cos@PLT
cvtsi2sd %edi, %xmm1https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI
If the callee wishes to use registers RBX, RBP, and R12–R15, it must restore their original values before returning control to the caller. All other registers must be saved by the caller if it wishes to preserve their values.
=>
push %rdi call cos@PLT pop %rdi cvtsi2sd %edi, %xmm1
Wobei...
Das is vermutlich auch falsch, wegenStack aligned on 16 bytes boundary. 128 bytes red zone below stack.
Die red zone ist jetzt kein Problem, aber das 16 Byte Alignment halten wir jetzt nicht ein. Andrerseits scheinen sich GCC, Clang, ICC auch nicht so wirklich darum zu scheren und generieren Code mit bloss 1x
push
gefolgt voncall
. Ich bin verwirrt.Falls mir das jmd. erklären kann (also was das "Stack aligned on 16 bytes boundary" bedeutet/warum es dann doch kein Problem ist mit um 8 verschobenem Stack-Pointer eine andere Funktion aufzurufen...).
-
Dieser Beitrag wurde gelöscht!
-
Also an Pi könnte man auch so herankommen:
#include <stdio.h> #include <math.h> double getpi() { int i = 0; double d = 1; while (i <= 100000) { if ((i % 2) == 0) { d -= (1.0 / ((i * 2) + 3)); } else { d += (1.0 / ((i * 2) + 3)); } i++; } return d / 45.0; } int getxy(int b, int w) { int x; float w2 = w * getpi(); if (b) x = (int)(25.0 * cos(w2)); else x = (int)(25.0 * sin(w2)); return x; } void get_kreis(int to) { int i = 0, x, y, x2, y2; while (i <= to) { x = getxy(1, i); y = getxy(0, i); if (x != x2 || y != y2) { printf("i=%03d x=%03d y=%03d\n", i, x, y); } x2 = x; y2 = y; i++; } printf("ready.\n"); } int main() { get_kreis(720); }
Das fände ich sogar noch besser, da man nicht durch 180 teilen muss. Aber das in Assembler macht keinen Spaß...
-
??? Was ist dein Ziel? Pi brauchst du gar nicht selber zu berechnen, außer das soll eine Übungsaufgabe für Assembler sein, Pi zu berechnen. Und dann wäre es geschummelt, den Cosinus einzusetzen.
-
@SeppJ Er berechnet Pi doch eh über so ne lustige unendliche Summenformel ganz ohne Cosinus.
Wo du natürlich Recht hast: man braucht das nicht selbst zu implementieren, man kann auch einfach nen Taschenrechner/Wikipedia nehmen, die Zahl dann in die erstbeste IEEE float converter Webseite reinpasten und die Hex-Konstante aus dieser rauskopieren.
Bzw. evtl. kann der Assembler der Wahl auch float/double Konstanten. Keine Ahnung, ich kenn ich mit Computern nicht aus
-
@hustbaer sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Wikipedia nehmen, die Zahl dann in die erstbeste IEEE float converter Webseite reinpasten und die Hex-Konstante aus dieser rauskopieren.
Bzw. evtl. kann der Assembler der Wahl auch float/double Konstanten. Keine Ahnung, ich kenn ich mit Computern nicht ausDas habe ich ja getan. Also die float Konstante/Literal bestimmt; aber das Problem ist, x86_64 erlaubt glaube ich keine float Konstanten.
@SeppJ sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Was ist dein Ziel? Pi brauchst du gar nicht selber zu berechnen, außer das soll eine Übungsaufgabe für Assembler sein
Das ist eine Übungsaufgabe für Assembler. Ich hatte auch drüber nachgedacht, nicht alles in Assembler zu schreiben, sondern nur die Methodenrümpfe in Inline-Assembler... um sich die Aufruf-Konventionen zu sparen. Aber es ist quasi genau so schwer, wie alles in Asm zu schreiben.
Kann man denn sagen, die Assembler-Syntax ist immer von der konkreten Architektur des Prozessors abhängig?
Und was war zuerst da, Asm oder C?
-
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
, x86_64 erlaubt glaube ich keine float Konstanten.
Das hat nichts mit der Architektur zu tun, sondern mit dem Assembler (das Programm)
Kann man denn sagen, die Assembler-Syntax ist immer von der konkreten Architektur des Prozessors abhängig?
Ja. Und vom verwendeten Assembler (Programm).
Und was war zuerst da, Asm oder C?
Seit wann gibt es Computer?
Die allerersten hatten sicher kein AssemblerSeit wann gibt es C?
Vor C gab es schon Fortran.
-
Zitat Wikipedia:
C ist eine imperative und prozedurale Programmiersprache, die der Informatiker Dennis Ritchie in den frühen 1970er Jahren an den Bell Laboratories entwickelte. Seitdem ist sie eine der am weitesten verbreiteten Programmiersprachen.
Der erste Assembler wurde zwischen 1948 und 1950 von Nathaniel Rochester für eine IBM 701 geschrieben.