Gleitkommaoperationen

Aus C und Assembler mit Raspberry

In diesem Kapitel werden wir uns mit Gleitkommaoperationen im ARM64-Assembler beschäftigen. Die Schwerpunkte liegen auf den FPU-Registern (Floating Point Unit), einem Überblick über die Register und deren Verwendung in Funktionen. Ebenso werden wichtige Hinweise zur Verwendung der Register für Gleitkommaoperationen gegeben.

FPU-Register: Überblick

Unter ARM64 haben wir eine dedizierte Einheit für Gleitkommaoperationen, die sogenannte FPU (Floating Point Unit). Diese Einheit verwendet spezifische Register, um Gleitkommaoperationen auszuführen.

Registerüberblick

  • h0 - h31: Einfaches-Gleitkomma-Register (16-Bit)
  • s0 - s31: Einzelpräzisions-Gleitkomma-Register (32-Bit)
  • d0 - d31: Doppelpräzisions-Gleitkomma-Register (64-Bit)
  • q0 - q31: Vektorregister (128-Bit) (auch v0 - v31)

Der NEON-Coprozessor verwendet das V-Register. Allerdings kann der Coprozessor auch mit 128-Bit Ganzzahlen umgehen. Dies sind die gleichen Register, werden aber "Q"-Register genannt.

  • Register "d" ist ein Teil des Register "v", das Register "s" ein Teil des Registers "d" usw. Dies ist zu vergleichen wie bei den ARM-Registern "x" und "w".
Bits 127 - 112 111 - 96 95 - 80 79 - 64 63 - 48 47 - 32 31 - 16 15 - 0
V-Register (NEON)
Q-Register (FPU)
128-Bit
D-Register (FPU) 64-Bit
S-Register (FPU) 32-Bit
H-Register (FPU) 16-Bit

Der NEON-Coprozessor verwendet das V-Register. Allerdings kann der Coprozessor auch mit 128-Bit Ganzzahlen umgehen. Dies sind die gleichen Register, werden aber "Q"-Register genannt.

Diese Register werden für verschiedene Arten von Gleitkommaoperationen und SIMD (Single Instruction, Multiple Data) verwendet. Hier konzentrieren wir uns hauptsächlich auf die Register d0 bis d31 für Doppelpräzisions-Gleitkommaoperationen.

Verwendung der FPU-Register in Funktionen

Um Gleitkommaoperationen in ARM64-Assembler durchzuführen, sind bestimmte Anweisungen und Register zu beachten. Im Folgenden finden Sie grundlegende Beispiele und Best Practices für die Durchführung von Gleitkommaoperationen.

Beispiel: Gleitkommaaddition

.section .data
    var1: .double 3.14
    var2: .double 1.618

.section .text
.global _start

_start:
    // Lade Gleitkommawerte in d-Register
    ldr x0, =var1
    ldr d0, [x0]
    ldr x1, =var2
    ldr d1, [x1]

    // Führe die Gleitkommaaddition durch
    fadd d2, d0, d1  // d2 = d0 + d1

    // Beispiel: Rückgabe des Ergebnisses mit einem Systemaufruf 
    // (zur Vereinfachung hier nicht gezeigt, da Gleitkomma nicht direkt unterstützt wird)

    // Exit system call
    mov x8, #93       // syscall number for exit
    mov x0, #0        // exit code
    svc 0

Hinweise zur Verwendung von FPU-Registern

  • Laden und Speichern von Gleitkommawerten: Verwendung von Load/Store-Instruktionen wie ldr und str, um Werte in und aus den FPU-Registern zu laden bzw. zu speichern.
  • Arithmetische Operationen: Verwendung von speziellen Instruktionen wie fadd (Addition), fsub (Subtraktion), fmul (Multiplikation) und fdiv (Division) für Gleitkommaarithmetik.
  • Beachtung von Doppelpräzisionsregistern: Verwenden Sie d-Register für doppelpräzise Gleitkommazahlen.
  • Parameterübergabe: Gleitkommaargumente werden in den d0 bis d7 Registern übergeben, ähnlich wie Ganzzahlen in x0 bis x7.

Beispiel: Gleitkommamultiplikation in einer Funktion

Funktionsdefinition in C:

// float_functions.c
double multiply(double a, double b) {
    return a * b;
}

Aufruf der Funktion aus Assembler:

.extern multiply

.global _start

_start:
    // Parameter für die Funktion laden
    ldr x0, =var1
    ldr d0, [x0]
    ldr x1, =var2
    ldr d1, [x1]

    // Aufruf der C-Funktion
    bl multiply

    // Das Ergebnis ist in d0
    // Beispiel: Gleitkommaergebnis weiter verarbeiten
    // (hier nicht weiter verwendet)

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Funktionsdefinition im Assembler:

.global multiply

multiply:
    fmul d0, d0, d1   // Führe die Multiplikation durch: d0 = d0 * d1
    ret               // Rückkehr zur aufrufenden Funktion

Parameterübergabe und Rückgabewert

Wie bereits erwähnt, werden Gleitkommaargumente in den Registern d0 bis d7 übergeben und Rückgabewerte in d0. Hier ein Beispiel, das zeigt, wie Sie eine Gleitkommafuntionsdefinition in Assembler schreiben und von C aus aufrufen.

C-Beispiel:

// main.c
#include <stdio.h>

extern double asm_multiply(double a, double b);

int main() {
    double result = asm_multiply(3.14, 1.618);
    printf("Ergebnis: %f\n", result);
    return 0;
}

Assemblerfunktion:

.global asm_multiply

asm_multiply:
    fmul d0, d0, d1   // Führe die Multiplikation durch: d0 = d0 * d1
    ret               // Rückkehr zur aufrufenden Funktion

Kompilierung und Linken:

gcc -c main.c
as -o float_functions.o float_functions.s
gcc -o myprogram main.o float_functions.o

Beispiele für die Verwendung

Addition (fadd)

.section .data
var1: .double 3.14
var2: .double 1.618

.section .text
.global _start

_start:
    ldr x0, =var1
    ldr d0, [x0]        // Lade var1 in d0
    ldr x1, =var2
    ldr d1, [x1]        // Lade var2 in d1

    fadd d2, d0, d1     // d2 = d0 + d1 (3.14 + 1.618)

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Subtraktion (fsub)

.section .data
var1: .double 3.14
var2: .double 1.618

.section .text
.global _start

_start:
    ldr x0, =var1
    ldr d0, [x0]        // Lade var1 in d0
    ldr x1, =var2
    ldr d1, [x1]        // Lade var2 in d1

    fsub d2, d0, d1     // d2 = d0 - d1 (3.14 - 1.618)

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Multiplikation (fmul)

.section .data
var1: .double 3.14
var2: .double 1.618

.section .text
.global _start

_start:
    ldr x0, =var1
    ldr d0, [x0]        // Lade var1 in d0
    ldr x1, =var2
    ldr d1, [x1]        // Lade var2 in d1

    fmul d2, d0, d1     // d2 = d0 * d1 (3.14 * 1.618)

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Division (fdiv)

.section .data
var1: .double 3.14
var2: .double 1.618

.section .text
.global _start

_start:
    ldr x0, =var1
    ldr d0, [x0]        // Lade var1 in d0
    ldr x1, =var2
    ldr d1, [x1]        // Lade var2 in d1

    fdiv d2, d0, d1     // d2 = d0 / d1 (3.14 / 1.618)

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Konvertierung von Gleitkommazahlen

Ganzzahl zu Gleitkommazahl (scvtf / ucvtf)

.section .data
int_val: .word 10

.section .text
.global _start

_start:
    ldr w0, =int_val
    ldr w1, [w0]        // Lade int_val in w0

    scvtf d0, w1        // Wandle signed integer w1 in double d0 um

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Gleitkommazahl zu Ganzzahl (fcvtzs / fcvtzu)

.section .data
float_val: .double 3.14

.section .text
.global _start

_start:
    ldr x0, =float_val
    ldr d0, [x0]        // Lade float_val in d0

    fcvtzs w1, d0       // Wandle double d0 in signed integer w1 um

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Vergleich von Gleitkommazahlen und bedingte Sprungbefehle

Vergleich von Gleitkommazahlen (fcmpe / fcsel)

.section .data
var1: .double 3.14
var2: .double 1.618

.section .text
.global _start

_start:
    ldr x0, =var1
    ldr d0, [x0]        // Lade var1 in d0
    ldr x1, =var2
    ldr d1, [x1]        // Lade var2 in d1

    fcmpe d0, d1        // Vergleiche d0 und d1
    b.gt greater        // Springe zu 'greater', wenn d0 > d1

    // Kleiner oder gleich Fall
 le_or_eq:
    // Hierher wird gesprungen, wenn d0 <= d1
    // (Z.B. Füge Code für diesen Fall hinzu)
    b exit

greater:
    // Hierher wird gesprungen, wenn d0 > d1
    // (Z.B. Füge Code für diesen Fall hinzu)

 exit:
    // Exit system call
    mov x8, #93
    mov x0, #0
    svc 0

Wichtiger Hinweis:

  • fcmpe: Vergleich von Gleitkommazahlen nach IEEE 754.
  • fcmp: Vergleich von Gleitkommazahlen ohne Exception (unsicherer Vergleich).

Beide Vergleichsoperationen verwenden die gleichen Vergleichsflags (N, Z, C, V), wie bei Ganzzahlenvergleichen.


FPU-Register

v0 - v31: 128-Bit d0 - d31: 64-Bit (double floating Point) s0 - s31: 32-Bit (single) h0 - h31: 16-Bit (float)

  • Register "d" ist ein Teil des REgister "v", das Register "s" ein Teil des Registers "d" usw. Dies ist zu vergleichen wie bei den ARM-Registern "x" und "w".
Bits 127 - 112 111 - 96 95 - 80 79 - 64 63 - 48 47 - 32 31 - 16 15 - 0
V-Register (NEON) 128-Bit
D-Register (FPU) 64-Bit
S-Register (FPU) 32-Bit
H-Register (FPU) 16-Bit

Der NEON-Coprozessor verwendet das V-Register. Allerdings kann der Coprozessor auch mit 128-Bit Ganzzahlen umgehen. Dies sind die gleichen Register, werden aber "Q"-Register genannt.

Verwendung der Register in Funktionen

Auch hier müssen bestimmte Register auf dem Stack gesichert werden, da die Aufrufende Funktion davon ausgeht, dass diese nicht verloren gehen, wenn sie eine Funktion aufruft.

Davon sind die Register V8-V15 betroffen. Werden diese verwendet, müssen diese auf dem Stack gesichert werden und am ende der Funktion wieder hergestellt werden.

Beispiel:

stp q8,q9,[sp,#-32]!
str q10, [sp,#16]!
ldr q10,[sp],#16
ldp q8,q9,[sp],#32

Arbeiten mit FPU-Registern

  • Verwendung von ldr und str
  • Kopieren von ARM-Register mit FPU-Registern: fmov
fadd rd, rn, rm // rd = rn + rm
fsub rd, rn, rm // rd = rn - rm
fmul rd, rn, rm // rd = rn * rm
fdiv rd, rn, rm // rd = rn / rm
fmadd rd, rn, rm, ra // rd = ra + rm * rn
fmsub rd, rn, rm, ra // rd = ra – rm * rn
fneg rd, rn // rd = -rn
fabs rd, rn // rd = Absuluter Wert ( rn )
fmax rd, rn, rm // rd = Max( rn, rm )
fmin rd, rn, rm // rd = Min( rn, rm )
fsqrt rd, rn // rd = Quadratwurzel( rn )

Für "r" können alle FPU-Register (d,s,h) verwendet werden

Konvertierung von Gleitkommazahlen

Beispiel:

fcvt d0, s1 // s1 -> d0
fcvt s1, d0 // d0 -> s1
fcvt s1, h2 // h2 -> s1
fcvt h2, s1 // s1 -> h2
  • Integer nach Gleitkommazahl:

-> (Un/Signed Convert to Floating Point)

SCVTF <Sd>, <Wn>: Konvertiert einen 32-Bit-Ganzzahlwert ('<Wn>' ) in eine 32-Bit-Single-Precision-Floatzahl ('<Sd>' ).

SCVTF

, <Wn>: Konvertiert einen 32-Bit-Ganzzahlwert ('<Wn>' ) in eine 64-Bit-Double-Precision-Floatzahl ('
' ). SCVTF <Sd>, <Xn>: Konvertiert einen 64-Bit-Ganzzahlwert ('<Xn>' ) in eine 32-Bit-Single-Precision-Floatzahl ('<Sd>' ). SCVTF
, <Xn>: Konvertiert einen 64-Bit-Ganzzahlwert ('<Xn>' ) in eine 64-Bit-Double-Precision-Floatzahl ('
' ).
  • Gleitkommazahlen nach Integer:
FCVTxx Instruktionen FCVTZS (Floating-point Convert to Signed integer, rounding toward Zero): Konvertiert eine Gleitkommazahl in eine vorzeichenbehaftete Ganzzahl mittels Abrundung in Richtung Null (Truncation). ASSEMBLY Copy code FCVTZS <Wd>, <Sn> // Konvertiert eine Single-Precision Gleitkommazahl (<Sn>) in eine 32-Bit Ganzzahl (<Wd>). FCVTZS <Xd>, <Sn> // Konvertiert eine Single-Precision Gleitkommazahl (<Sn>) in eine 64-Bit Ganzzahl (<Xd>). FCVTZS <Wd>, <Dn> // Konvertiert eine Double-Precision Gleitkommazahl (<Dn>) in eine 32-Bit Ganzzahl (<Wd>). FCVTZS <Xd>, <Dn> // Konvertiert eine Double-Precision Gleitkommazahl (<Dn>) in eine 64-Bit Ganzzahl (<Xd>). FCVTZS <Vd>.<T>, <Vn>.<T> // SIMD: Konvertiert Gleitkommazahlen in Ganzzahlen, z.B. <Vd>.4S, <Vn>.4S. FCVTZU (Floating-point Convert to Unsigned integer, rounding toward Zero): Konvertiert eine Gleitkommazahl in eine vorzeichenlose Ganzzahl durch Abrundung in Richtung Null. ASSEMBLY Copy code FCVTZU <Wd>, <Sn> // Konvertiert eine Single-Precision Gleitkommazahl (<Sn>) in eine 32-Bit Unsigned Ganzzahl (<Wd>). FCVTZU <Xd>, <Sn> // Konvertiert eine Single-Precision Gleitkommazahl (<Sn>) in eine 64-Bit Unsigned Ganzzahl (<Xd>). FCVTZU <Wd>, <Dn> // Konvertiert eine Double-Precision Gleitkommazahl (<Dn>) in eine 32-Bit Unsigned Ganzzahl (<Wd>). FCVTZU <Xd>, <Dn> // Konvertiert eine Double-Precision Gleitkommazahl (<Dn>) in eine 64-Bit Unsigned Ganzzahl (<Xd>). FCVTZU <Vd>.<T>, <Vn>.<T> // SIMD: Konvertiert Gleitkommazahlen in Unsigned Ganzzahlen, z.B. <Vd>.4S, <Vn>.4S. Verwendung und Syntax Die FCVTZS und FCVTZU Instruktionen können sowohl auf skalare Werte als auch auf SIMD-Vektoren angewendet werden. Beispiele für die Nutzung: ASSEMBLY Copy code // Skalare Konvertierungen FCVTZS W0, S1 // Konvertiert die Single-Precision Gleitkommazahl in S1 zu einer 32-Bit Ganzzahl und speichert sie in W0. FCVTZS X1, D2 // Konvertiert die Double-Precision Gleitkommazahl in D2 zu einer 64-Bit Ganzzahl und speichert sie in X1. FCVTZU W0, S1 // Konvertiert die Single-Precision Gleitkommazahl in S1 zu einer 32-Bit Unsigned Ganzzahl und speichert sie in W0. FCVTZU X1, D2 // Konvertiert die Double-Precision Gleitkommazahl in D2 zu einer 64-Bit Unsigned Ganzzahl und speichert sie in X1. // SIMD Vektor-Konvertierungen FCVTZS V0.4S, V1.4S // Konvertiert vier Single-Precision Gleitkommazahlen in V1 zu vier 32-Bit Ganzzahlen und speichert sie in V0. FCVTZU V0.2D, V1.2D // Konvertiert zwei Double-Precision Gleitkommazahlen in V1 zu zwei 64-Bit Unsigned Ganzzahlen und speichert sie in V0. Zusammenfassung: FCVTZS und FCVTZU sind die Hauptinstruktionen für die Konvertierung von Gleitkomma- zu Ganzzahlwerten. Diese Instruktionen unterstützen sowohl 32-Bit und 64-Bit Konvertierungen als auch SIMD-Vektoroperationen. In der ARM64-Architektur gibt es eine Reihe von FCVTxx-Instruktionen, die Gleitkommazahlen in Ganzzahlen umwandeln, abhängig von verschiedenen Rundungsmodi. Hier ist eine detaillierte Übersicht über diese Instruktionen und ihre Funktion: Übersicht der FCVTxx-Instruktionen FCVTAS (Floating-point Convert to Signed integer, rounding to nearest with ties to Away from zero): Wandelt eine Gleitkommazahl in eine vorzeichenbehaftete Ganzzahl mittels Rundung zum nächsten ganzzahligen Wert, wobei Rundungsbindungen vom Nullpunkt weg gerundet werden. FCVTAS <Wd>, <Sn> FCVTAS <Xd>, <Sn> FCVTAS <Wd>, <Dn> FCVTAS <Xd>, <Dn> FCVTAS <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTAU (Floating-point Convert to Unsigned integer, rounding to nearest with ties to Away from zero): Wandelt eine Gleitkommazahl in eine vorzeichenlose Ganzzahl mittels Rundung zum nächsten ganzzahligen Wert, wobei Rundungsbindungen vom Nullpunkt weg gerundet werden. FCVTAU <Wd>, <Sn> FCVTAU <Xd>, <Sn> FCVTAU <Wd>, <Dn> FCVTAU <Xd>, <Dn> FCVTAU <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTMS (Floating-point Convert to Signed integer, rounding toward Minus infinity): Wandelt eine Gleitkommazahl in eine vorzeichenbehaftete Ganzzahl mittels Abrundung zum nächsten kleinen Wert (Rundung gegen Minus-Unendlichkeit). FCVTMS <Wd>, <Sn> FCVTMS <Xd>, <Sn> FCVTMS <Wd>, <Dn> FCVTMS <Xd>, <Dn> FCVTMS <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTMU (Floating-point Convert to Unsigned integer, rounding toward Minus infinity): Wandelt eine Gleitkommazahl in eine vorzeichenlose Ganzzahl mittels Abrundung zum nächsten kleinen Wert (Rundung gegen Minus-Unendlichkeit). FCVTMU <Wd>, <Sn> FCVTMU <Xd>, <Sn> FCVTMU <Wd>, <Dn> FCVTMU <Xd>, <Dn> FCVTMU <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTPS (Floating-point Convert to Signed integer, rounding toward Plus infinity): Wandelt eine Gleitkommazahl in eine vorzeichenbehaftete Ganzzahl mittels Aufrundung zum nächsten größeren Wert (Rundung gegen Plus-Unendlichkeit). FCVTPS <Wd>, <Sn> FCVTPS <Xd>, <Sn> FCVTPS <Wd>, <Dn> FCVTPS <Xd>, <Dn> FCVTPS <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTPU (Floating-point Convert to Unsigned integer, rounding toward Plus infinity): Wandelt eine Gleitkommazahl in eine vorzeichenlose Ganzzahl mittels Aufrundung zum nächsten größeren Wert (Rundung gegen Plus-Unendlichkeit). FCVTPU <Wd>, <Sn> FCVTPU <Xd>, <Sn> FCVTPU <Wd>, <Dn> FCVTPU <Xd>, <Dn> FCVTPU <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTZS (Floating-point Convert to Signed integer, rounding toward Zero): Wandelt eine Gleitkommazahl in eine vorzeichenbehaftete Ganzzahl mittels Abrundung in Richtung Null (Truncation). FCVTZS <Wd>, <Sn> FCVTZS <Xd>, <Sn> FCVTZS <Wd>, <Dn> FCVTZS <Xd>, <Dn> FCVTZS <Vd>.<T>, <Vn>.<T> // SIMD-Version FCVTZU (Floating-point Convert to Unsigned integer, rounding toward Zero): Wandelt eine Gleitkommazahl in eine vorzeichenlose Ganzzahl durch Abrundung in Richtung Null (Truncation). FCVTZU <Wd>, <Sn> FCVTZU <Xd>, <Sn> FCVTZU <Wd>, <Dn> FCVTZU <Xd>, <Dn> FCVTZU <Vd>.<T>, <Vn>.<T> // SIMD-Version Zusammenfassung der Konvertierungsmodi: Rundung zur nächsten Ganzzahl: FCVTAS / FCVTAU: Rundung zum nächsten Wert mit Bindungen vom Nullpunkt weg. Rundung gegen Minus-Unendlichkeit: FCVTMS / FCVTMU: Abrundung zum nächsten kleineren Wert. Rundung gegen Plus-Unendlichkeit: FCVTPS / FCVTPU: Aufrundung zum nächsten größeren Wert. Rundung gegen Null: FCVTZS / FCVTZU: Abrundung in Richtung Null. Diese Sammlung von FCVTxx-Instruktionen bietet eine breite Palette von Optionen für die Konvertierung von Gleitkommazahlen in Ganzzahlen, je nach den spezifischen Rundungsanforderungen.

Vergleichen

FCMP Hd, Hm FCMP Hd, #0.0 FCMP Sd, Sm FCMP Sd, #0.0 FCMP Dd, Dm FCMP Dd, #0.0