linux x86-64 calling convention + FP Befehle

19/11/2009 - 16:44 von Markus Wichmann | Report spam
Hi all,

ich habe jüngst die Betriebssystemarchitektur gewechselt und stehe nun
vor dem Problem, dass ich all mein Assembler-Zeugs neu schreiben darf.

Und als wàre das noch nicht schlimm genug, hat sich nun auch die calling
convention geàndert. Ich gebe zu, sie nicht verstanden zu haben. Und
zwar gibt es in den ABI-Docs ein Beispiel:
http://www.x86-64.org/documentation/abi-0.99.pdf

typedef struct {
int a, b;
double d;
} structparm;

Dieses wird jetzt einer Funktion übergeben. Nun ist es so, dass das
Alignment dieser Struktur so groß ist wie das des Members mit dem
größten Alignment. Das ist in diesem Falle d. Das Alignment der Struktur
ist also 8 (jeder Member wird 8-Byte-aligned).

Das heißt, dass hinter a und b jeweils 4 Padding-Bytes liegen, und die
Struktur insgesamt 3 Eightbytes groß ist.

Nun wird eine solche Struktur einer Funktion übergeben. Dazu muss man
die Struktur erst einmal klassifizieren. OK, sie ist kleiner als 4
Eightbytes, aber größer als ein einziges, also wird jedes Eightbyte für
sich bewertet. Damit haben die ersten zwei Eightbytes die Klasse
INTEGER, das letzte SSE. Und dann kommt noch ein Post Merger Cleanup,
nachdem die ganze Struktur im Speicher übergeben werden soll. Stünde das
double vorne, würde die Doppelklassifizierung bleiben.

OK, aber selbst dann haben wir zwei Werte von Typ INTEGER, die
eigentlich in separate Register geladen werden müssten. Im Beispiel
jedoch werden beide in das gleiche Regiter geladen. Was habe ich
übersehen?

Und etwas ganz anderes: double und float gehören hiernach zur Klasse SSE
und kommen in SSE-Register, sofern noch welche übrig sind. long double
jedoch gehört zur Klasse X87, was im Speicher übergeben wird.

Nun will ich die C-Standard-Lib nachbauen. Da ibt es hàufig solche
Dreigestirne wie sin, sinf und sinl. Bislang konnte ich diese Funktionen
so bauen, dass sie jeweils ihre Operanden in Register laden, um
anschließend einen gemeinsamen Body anzuspringen (bzw. aufzurufen und
das Ergebnis wieder richtig zurückgeben). Natürlich hab ich mir den
Ärger bei Funktionen wie fabs gespart, weil da der Body aus genau einer
Anweisung bestanden hàtte.

Jetzt ist es ja aber so, dass zwei dieser Funktionen ihre Elemente schon
in SSE-Registern haben. Hmm... kann man die direkt in die FPU laden,
ohne Umweg über den Speicher? Wenn nicht, gibt es einen anderen àhnlich
beschaffenen Befehlssatz wie den der FPU für SSE- oder MMX-Register?

Was ich nicht weiß: Ist auf einer 64bit-CPU eine Software-Emulation der
FPU wirklich langsamer als die FPU selbst?

Also nochmal: Ich habe jetzt einen Quelloperanden in xmm0. Was kann ich
mit dem machen, was nicht? Geht sowas wie Wurzel ziehen? Logarithmus?
Exponentierung? Trigonometrie? Komplexe Trigonometrie? Oder muss ich den
Operanden dafür in die FPU laden?

Ach ja, und noch was: float ist ja bekanntlich nur halb so groß wie
double. Wenn jetzt beide in "das nàchste Freie SSE-Register" kommen,
heißt das dann, dass von den 128 Bit im einen Fall nur 64 und im anderen
nur 32 genutzt werden?

Tschö,
Markus

Nur weil ein Genie nix reißt, muß ja nun nicht gleich jeder Idiot
pausieren... Bully hats ja auch geschafft.
 

Lesen sie die antworten

#1 Jan Seiffert
19/11/2009 - 20:10 | Warnen spam
Markus Wichmann schrieb:
Hi all,

ich habe jüngst die Betriebssystemarchitektur gewechselt und stehe nun vor
dem Problem, dass ich all mein Assembler-Zeugs neu schreiben darf.

Und als wàre das noch nicht schlimm genug, hat sich nun auch die calling
convention geàndert. Ich gebe zu, sie nicht verstanden zu haben. Und zwar
gibt es in den ABI-Docs ein Beispiel:
http://www.x86-64.org/documentation/abi-0.99.pdf

typedef struct { int a, b; double d; } structparm;

Dieses wird jetzt einer Funktion übergeben. Nun ist es so, dass das Alignment
dieser Struktur so groß ist wie das des Members mit dem größten Alignment.
Das ist in diesem Falle d. Das Alignment der Struktur ist also 8 (jeder
Member wird 8-Byte-aligned).

Das heißt, dass hinter a und b jeweils 4 Padding-Bytes liegen, und die
Struktur insgesamt 3 Eightbytes groß ist.




Oehm, sicher das jedes Member 8-Byte-aligned wird?
Es gibt einen Unerschied wie die Struktur und wie ihre Member aligned werden.
Das alignment der Struktur ist wichtig, wenn du mehrere Struckturen, also ein
Array hasst.
Ich wuerde hier die Member so anordnen:
0 - int a
4 - int b
8 - double d
16 - naechstes struct

Also kein Padding irgendwo noetig.

Nun wird eine solche Struktur einer Funktion übergeben. Dazu muss man die
Struktur erst einmal klassifizieren. OK, sie ist kleiner als 4 Eightbytes,
aber größer als ein einziges, also wird jedes Eightbyte für sich bewertet.
Damit haben die ersten zwei Eightbytes die Klasse INTEGER, das letzte SSE.
Und dann kommt noch ein Post Merger Cleanup, nachdem die ganze Struktur im
Speicher übergeben werden soll. Stünde das double vorne, würde die
Doppelklassifizierung bleiben.

OK, aber selbst dann haben wir zwei Werte von Typ INTEGER, die eigentlich in
separate Register geladen werden müssten. Im Beispiel jedoch werden beide in
das gleiche Regiter geladen. Was habe ich übersehen?




Ja, dass das unsinnig ist.
Schau dir mal an, was der Compiler daraus baut.
Am besten mit getrennten Source files, so das er da keine wilden Optimierungen
macht.

Und etwas ganz anderes: double und float gehören hiernach zur Klasse SSE und
kommen in SSE-Register, sofern noch welche übrig sind. long double jedoch
gehört zur Klasse X87, was im Speicher übergeben wird.

Nun will ich die C-Standard-Lib nachbauen. Da ibt es hàufig solche
Dreigestirne wie sin, sinf und sinl. Bislang konnte ich diese Funktionen so
bauen, dass sie jeweils ihre Operanden in Register laden, um anschließend
einen gemeinsamen Body anzuspringen (bzw. aufzurufen und das Ergebnis wieder
richtig zurückgeben). Natürlich hab ich mir den Ärger bei Funktionen wie fabs
gespart, weil da der Body aus genau einer Anweisung bestanden hàtte.

Jetzt ist es ja aber so, dass zwei dieser Funktionen ihre Elemente schon in
SSE-Registern haben. Hmm... kann man die direkt in die FPU laden, ohne Umweg
über den Speicher?



Nein, bedankt dich bei Intel wie immer unsymetrische Befehlssaetze zu bauen.
(Du kannst von SSE nach MMX (MOVQ2Q) und zurueck (MOVQ2DQ), aber dann is nix mit
FPU)

Wenn nicht, gibt es einen anderen àhnlich beschaffenen Befehlssatz wie den
der FPU für SSE- oder MMX-Register?




Seit SSE2 gibt es alle "wichtigen" Befehle fuer Float und Double als "packed"
(also zwei oder vier Datum in einem Register) und als scalar (nur das unterste
Element), so das man die "ueblichen" FP operationen in SSE Registern machen kann.

Was ich nicht weiß: Ist auf einer 64bit-CPU eine Software-Emulation der FPU
wirklich langsamer als die FPU selbst?




Der Vorteil ist eingentlich:
Man hat nicht dieses Stack gewurgel (besser fuer Register Allokatoren von
Compilern) und 16 statt 8 Register.
Das wars dann aber auch schon.

Also nochmal: Ich habe jetzt einen Quelloperanden in xmm0. Was kann ich mit
dem machen, was nicht?



Komplexe Operationen wie ...
Geht sowas wie Wurzel ziehen? Logarithmus?
Exponentierung? Trigonometrie? Komplexe Trigonometrie?



... sind dann aber ein PITA.

Wurzel ja. (SQRTSS, SQRTSD)
Es gibt noch irgendwas mit dem Reziprok.
Alles andere...


Oder muss ich den Operanden dafür in die FPU laden?




Du kannst es halt dann Zufuss ausrechnen (IEEE Mathe Genies bitte vortreten)
oder in die FPU laden.

Ich hatte mal irgendwo eine SSE Mathebibliothek mit dem ganzen kram gesehen, da
ging es darum mehere Werte auf einmal zu berechnen, aber das koennte man ja auf
einen Wert runterbrechen.

Ach ja, und noch was: float ist ja bekanntlich nur halb so groß wie double.
Wenn jetzt beide in "das nàchste Freie SSE-Register" kommen, heißt das dann,
dass von den 128 Bit im einen Fall nur 64 und im anderen nur 32 genutzt
werden?




Jupp, so ist die Idee, man arbeitet dann nur mit dem untersten Element (scalar).

Tschö, Markus



Gruss
Jan

Fachbegriffe der Informatik:
44: Verdeckter Fehler
Siemens hat mitentwickelt. (Jörg Pechau)

Ähnliche fragen