Seite 3 von 4

Verfasst: 23.07.2006 12:04
von remi_meier
Ich frage mich gerade, ob es für die FPU auch Aufrufkonventionsregeln
bei stdcall & Co. gibt. Glaub nicht, oder? PB leert auf jeden Fall den Stack
nicht und erwartet auch, dass der wieder so zurück kommt, wie er vor
dem Aufruf da war. Wenn du also da FNINIT benutzt, könnts zu schwer
auffindbaren Fehlern führen (auch so, da der Stack ja sehr begrenzt ist).

Bis jetzt habe ich bei den Aufrufkonventionen immer nur für die normalen
Register Regeln gefunden, auch nicht für SSE Register.


EDIT: Noch ein kleines Beispiel zum Verdeutlichen:

Code: Alles auswählen

Procedure.f d()
  !FNINIT
  !FLD1
  ProcedureReturn
EndProcedure

a.f = d() + d()
Debug a
Übrigens wurde der Bug schon in Form von rekursiver Berechnung von
Fibonacci-Zahlen per Floats gemeldet. kA ob sich da in naher Zukunft
was ändern wird.

Verfasst: 23.07.2006 13:37
von Helle
Da sich weder PB noch FAsm um FPU-Register bzw. -Environment kümmern (wie Remi schon richtig schrieb), folgender Hinweis: Meine Beispiele bitte nur als solche auffassen oder eben als eigenständiges Programm (also mit "END" und aus).
Innerhalb eines Programmes sollte man sich tatsächlich um Sicherungen bezüglich der FPU kümmern. Dazu folgendes Beispiel:

Code: Alles auswählen

;-------- Umrechnung Float in DWord nach IEEE-754 einfache Genauigkeit 32-Bit, gerundet
;-------- "Helle" Klaus Helbing PB4.0   23.07.2006
;-------- Bit 31:Vorzeichen, Bit 23-30:Exponent, Bit 0-22:Mantisse
;-------- Exponent ist für 2-er Potenzen! Für 12.45 gilt E=3, da 2 hoch 3 gleich 8 die nächsttiefere 2er Potenz ist
;-------- 12.45 ist also 8x1.55625. Die Mantisse wird "normiert", so dass sie als 0.55625 erscheint.
;-------- Auch die Mantisse wird in 2-Potenzen aufgespalten; d.h. das erste Bit (Stelle 22) hat im gesetzten
;-------- Fall den Wert 0.5 (1/2); das Bit an Stelle 21 hat (gesetzt) den Wert 0.25 (1/4) usw.

Global F.f             ;Umrechnungs-Zahl 
Global M.l             ;Mantisse
Global E.l             ;Exponent
Global A.l = 127       ;Additionswert für einfache Genauigkeit (32-Bit)    
Global X.l             ;Bildschirmausgabe
Global FPUSich.l       ;Reservierter Speicher für FPU-Sicherung

 Input$ = InputRequester("Eingabe", "Bitte geben Sie eine Dezimalzahl ein (Punkt statt Komma !):", "")

  If Input$ > ""
    F = ValF(Input$)
   Else 
    End 
  EndIf

  FPUSich = AllocateMemory(108)        ;108 Bytes werden benötigt
    If FPUSich = 0 
      MessageRequester("Fehler!","Konnte den angeforderten Speicher nicht reservieren!") 
    EndIf 

!MOV esi,[v_FPUSich]   ;ESI zeigt auf den reservierten Speicherbereich von 108 Bytes, der für die Sicherung
                       ;von Umgebung und Register der FPU benötigt wird   
                            
!fsave [esi]           ;kümmert sich im Gegensatz zu FNSAVE auch um evtl. anhängige Exceptions! 
                       ;beinhaltet automatisch FNINIT. Sichert Umgebung (div.Register und Kontrollwort)
                       ;und eben die 8 Rechen-Register  
!fld [v_F]
!fxtract               ;Zerlegung einer Realzahl in Mantisse (st0) und Exponent (st1)
!fst [v_M]             ;In Bit 0-22 steht die Mantisse

!fld st1
!fiadd [v_A]
!fist [v_E]            ;Exponent+127, hier also 3+127=130

!frstor [esi]          ;Alles mit FSAVE Gesicherte zurück

!XOR eax,eax
!MOV eax,[v_E]
!SHL eax,23            ;Exponent an "richtige" Stelle schieben
!MOV edx,[v_M]
!AND edx,807fffffh     ;23 Bit der Mantisse und Vorzeichen verwenden
!ADD eax,edx
!MOV [v_X],eax

MessageRequester("Umrechnung Float in DWord", "Das Ergebnis für " + Input$ + " lautet: "  + #CRLF$ + #CRLF$ + "Dez: " + Str(X) + #CRLF$ + "Hex: " + Hex(X) + #CRLF$ + "Bin:  " + RSet(Bin(X), 32, "0"))
Remi´s kurzes Beispiel könnte man folgenderweise behandeln:

Code: Alles auswählen

Global FPUSich.l  

  FPUSich = AllocateMemory(108) 
  !MOV esi,[v_FPUSich]

Debug FPUSich      ;Kontrolle!

Procedure.f d() 
   !FLD1 
  ProcedureReturn 
EndProcedure 

!FSAVE [esi]
a.f = d() + d() 
!FRSTOR [esi]

Debug a

!MOV [v_FPUSich],esi

Debug FPUSich      ;Kontrolle!
Ich hoffe, Remi ist endlich mal zufrieden mit mir :mrgreen: !

Gruss
Helle

Verfasst: 23.07.2006 13:58
von AndyX
wow, danke Helle und Remi für die ganzen Erklärungen! :D

Verfasst: 23.07.2006 14:20
von remi_meier
> Ich hoffe, Remi ist endlich mal zufrieden mit mir :mrgreen: !
lol, wie wenn ich dein Mentor sein könnte :lol:

Das von dir konvertierte Beispiel zeigt leider aber nicht den Nutzen dieser
Befehle (FSAVE etc.), da es auch ohne laufen würde :D
Es wäre eher so gedacht, nehme ich an:

Code: Alles auswählen

Global FPUSich.l  

FPUSich = AllocateMemory(108) 
!MOV Esi,[v_FPUSich] 

Debug FPUSich      ;Kontrolle! 

Procedure.f d() 
  !MOV Ecx, [v_FPUSich]
  !FSAVE [Ecx]
  ; irgendwelche Berechnungen mit ganzem Stack zur Verfügung
  !FRSTOR [Ecx] 
  
  !FLD1 ; Resultat der Berechnungen auf fPU-Stack legen
  
  ProcedureReturn 
EndProcedure 

a.f = d() + d() 

Debug a 

!MOV [v_FPUSich],Esi 

Debug FPUSich      ;Kontrolle!
Wobei ev. der Status nicht in einer globalen, sondern lokalen Variable
gespeichert werden sollte, für verschachtelte Aufrufe. Aber das nur noch
so nebenbei als Ergänzung.

Verfasst: 23.07.2006 14:40
von Helle
Nö, das Positionieren von Save und Restore war schon Absicht! Dein Beispiel funktioniert auch ohne Klimmzüge, weil FLD1 nichts weiter macht als den TOS mit 1 zu laden und dies so zurückgibt. Aber Dein letztes Beispiel liefert z.B. veränderte Flags zurück und der Stackpointer wird auch dekrementiert !

Gruss
Helle

Verfasst: 23.07.2006 14:55
von remi_meier
Jo, das ist schon klar. Aber mit meinem Beispiel wollte ich verdeutlichen,
dass ein einfaches d()+d() ein falsches Resultat liefern kann, wenn man
FINIT verwendet. Der Vorteil an FINIT ist ja nunmal, dass man so alle
FPU-Register wieder frei hat und mit allen Rechnen kann. Um das gleiche
zu erreichen, ohne dass es ein falsches Resultat gibt, müssen eben
FSAVE und FRSTOR innerhalb der Prozedur stehen, damit man da einen
Bereich hat, wo man alle Register zur Verfügung hat.
Mein d()+d() Beispiel ist ja eigentlich noch wenig stackintensiv und deshalb
ist ein FINIT oder FSAVE-FRSTOR auch noch nicht nötig, aber ich denke,
dass es Situationen gibt, bei denen PB in _einem_ Ausdruck noch ein
wenig mehr Register benötigt. Wenn dann die Prozedur auch noch einige
Benötigt, haben wir wieder den Stacküberlauf der FPU. Das könnte mit
FSAVE-FRSTOR verhindert werden.

Also haben wir mit unseren Beispielen verschiedene Sachen zeigen wollen :allright:

Verfasst: 23.07.2006 15:15
von remi_meier
Habe hierzu mal einen einfachen Code zur Demonstration von PBs Limi-
tation zusammengezimmert. Er zeigt, warum FSAVE und FRSTOR sehr
nützlich sein können. Eigentlich zeigt er das Problem, welches ich mit
meinem Beispiel zeigen wollte:

Code: Alles auswählen

Procedure.f FibPB(n.l)
  If n <= 2
    ProcedureReturn 1.0
  EndIf
  
  ProcedureReturn FibPB(n - 1) + FibPB(n - 2)
EndProcedure

Procedure.f FibME(n.l)
  Protected *mem
  Protected res.f
  
  If n <= 2
    ProcedureReturn 1.0
  EndIf
  
  *mem = AllocateMemory(108)
  
  !MOV Ecx, [p.p_mem]
  !FSAVE [Ecx] ; SAVE, ein freier FPU-Stack für den nächsten
               ; Funktionsaufruf
  
  res = FibME(n - 1) + FibME(n - 2)
  
  !MOV Ecx, [p.p_mem]
  !FRSTOR [Ecx] ; RESTORE
  
  FreeMemory(*mem)
  ProcedureReturn res.f
EndProcedure



Debug "Fib per PB"
For z = 1 To 20
  Debug FibPB(z)
Next

Debug "Fib mit FPU-Save"
For z = 1 To 20
  Debug FibME(z)
Next

Verfasst: 23.07.2006 15:49
von Helle
@Remi: Sehr gut :allright: !
Ich hoffe, einige Leute haben Lust auf FPU-Programmierung bekommen!
Eigentlich wollte ich nur mal ´ne simple Addition per FPU zeigen... :D

Gruss
Helle

Verfasst: 26.08.2006 18:41
von remi_meier
Bin per Zufall über ein Dokument gestolpert über die Aufrufkonventionen.
Ich habe oben kurz die Frage aufgeworfen, ob es Regeln für den FPU-Stack
beim Aufrufen von Funktionen gibt, nun habe ich diese gefunden (weiss
nicht wie offiziell, aber sieht seriös aus):
Floating point registers
The floating point registers ST(0)-ST(7) need not be saved. These registers must be
emptied before any call or return, except for registers used for return values. The 64-bit
Microsoft compiler does not use ST(0)-ST(7). It is not allowed to use these registers in
kernel mode in 64-bit Windows.
Quelle: http://www.agner.org/optimize/calling_conventions.pdf

Damit verstösst PB gegen die Aufrufkonventionen.

Wollte das nur noch schnell zeigen, der Bug wurde ja schon vor einiger
Zeit gemeldet.

greetz
Remi

Verfasst: 26.08.2006 21:21
von MVXA
Ist doch aber schneller, wenn PB die Register nicht sichert. So lange es
damit umgehen kann, ist doch alles Butter, oder nicht?