Was tut dieser Assembly Code? (Anfängerfrage)



  • Habe jetzt Assemblercode der funktioniert aber das falsche Ergebnis für x ausgibt:

    	.text
    	.globl	getx
    	.type	getx, @function
    getx:
    .LFB0:
    	push		%rcx
    # main.c:7: 	float w2 = w * (M_PI / 180.0);
    # main.c:8: 	x = (int) (r * cos(w2));
    # main.c:9: 	return x;
    	movl			$0x4048f5c3, %ecx
    	movl			$0x43340000, %edx
    	cvtsi2sd	%ecx, %xmm0
    	cvtsi2sd	%edx, %xmm1
    	divsd		%xmm0, %xmm1
    	cvtsi2sd	%eax, %xmm0
    	mulsd		%xmm0, %xmm1
    	movsd			%xmm1, %xmm0
    	call		cos@PLT
    	movsd			%xmm0, %xmm1
    	cvtsi2sd	%ebx, %xmm0
    	mulsd		%xmm0, %xmm1
    	cvttsd2si	%xmm1, %eax
    	pop			%rcx
    	ret
    

    Caller:

    # main.c:24: 		x = getx(25, i);
    	movl	-4(%rbp), %ebx	# i, tmp87
    	movl		$25, %eax	#,
    	call	getx	#
    	movl	%eax, -16(%rbp)	# tmp88, x
    # main.c:25: 		y = gety(25, i);
    	movl	-4(%rbp), %eax	# i, tmp89
    	movl	%eax, %esi	# tmp89,
    	movl	$25, %edi	#,
    	call	gety	#
    	movl	%eax, -20(%rbp)	# tmp90, y
    

    Wieso ist das Ergebnis noch falsch?



  • @EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):

    In der Prüfung kann auch ein ganz anderer mc drankommen, es wird ein Teil der Doku mitgegeben.

    Dann wird das sicher etwas ziemlich simples sein. Ich finde aber so eine Ansage eher ... ähm ... pervers.
    Auf jeden Fall wird es mit ziemlicher Sicherheit nicht AT&T-Syntax sein.



  • Stimme Swordfish zu. Du willst jetzt speziell Floating-Point-Berechnung auf X8664 lernen, um Mikrocontroller-Assembler zu schreiben?!

    Jedenfalls: was tust du denn eigentlich in deiner Funktion?
    Du teilst 0x43340000 durch 0x4048f5c3 (oder andersrum, ich kommt bei at&t immer durcheinander) - das ergibt 1.0453936 und hat in jedem Fall nichts mit pi/180 zu tun. Wie kommst du überhaupt auf die beiden Zahlen in Zeile 10/11?



  • @EinNutzer0
    0x4048f5c3 ist die IEEE single precision float representation für 3.14.
    So funktioniert das aber nicht. cvtsi2sd interpretiert den Wert im Register als Integer.
    Laut Internetz kannst du movd dafür verwenden.
    Also statt

    	movl			$0x4048f5c3, %ecx
    	movl			$0x43340000, %edx
    	cvtsi2sd	%ecx, %xmm0
    	cvtsi2sd	%edx, %xmm1
    

    dann

    	movl			$0x4048f5c3, %ecx
    	movl			$0x43340000, %edx
    	movd			%ecx, %xmm0
    	movd			%edx, %xmm1
    

    Die weiteren cvtsi2sd müssen aber bleiben, denn da willst du ja wirklich nen Integer nach Float konvertieren.



  • Ich glaub jetzt inzwischen, die float literals sind das Problem... (Und ich hatte eax und ebx in getx vertauscht...)

    Hier hab ich eine interessante Quelle gefunden: https://www3.nd.edu/~dthain/courses/cse40243/fall2015/intel-intro.html

    https://a.uguu.se/bm1a5UpI4kHZ.png

    https://a.uguu.se/KKLJK0bxdZFI.png

    https://a.uguu.se/gNWD5HtWKcry.png

    Möglichkeit 2: Man verwende ein int literal und teile dies durch einen Divisor


  • Mod

    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, %xmm1

    Sofern 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 schonmal 180.0/PI, nicht PI/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 mit

    	cvtsi2sd	%eax, %xmm1
    	mulsd		%xmm0, %xmm1
    

    Davon abgesehen...
    ...wenn du mit 32 Bit floats in den XMM Registern arbeitest, ist divsd der falsche Befehl, denn der arbeitet mit doubles (=64 Bit). Du musst entweder divss verwenden oder halt mit 64 Bit floats (aka. doubles) arbeiten. Analog dazu die ganzen anderen sd 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-word

    Bei 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 nen double als Parameter. Die Variante mit single-precision heisst cosf.



  • Danke, hab alles nach double umgestellt - und divss oder divsd 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, dass cos 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 mit movl 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 und movq muss es dann leaveq 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, %xmm1

    https://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, wegen

    Stack 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 von call. 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...).



  • This post is deleted!


  • 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ß...


Log in to reply