Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Assembler-Rätsel (https://www.delphipraxis.net/203561-assembler-raetsel.html)

Maekkelrajter 29. Feb 2020 12:41

Assembler-Rätsel
 
Hallo,
Gelegentlich sehe ich mir die mit der CE mitgelieferten Sources an, teils aus Interesse, wie das eine oder andere Problem von Profis gelöst wird, aber natürlich auch, um Anregungen für eigene Entwicklungen zu finden. Dabei interessieren mich auch und gerade die Assembler-Implementierungen. Zu Turbopascal - Zeiten habe ich relativ vieles mit dem integrierten Assembler realisiert, was durchweg zu beachtlichen Performance-Steigerungen führte. Dabei war ich meilenweit entfernt davon, ein Assembler-'Crack' zu sein, was sich spätestens bei den ersten Versuchen mit Delphi 4 zeigte, als ich feststellen musste, daß in Delphi (Pascal) implementierte Routinen schneller waren als meine mühsam zusammengefrickelten und durchaus flotten Assembler - Sachen mit der gleichen Funktion :-(
Seitdem habe ich eigentlich nichts mehr mit Assembler realisiert. Dennoch schaue ich immer mal wieder in die Sources und versuche, den Ablauf zu verstehen, was mir zugegebenermaßen eher selten gelingt. Dabei stieß ich auf folgenden Code, der zu der Implementierung der 'Pos' - Funktion in der Unit 'System' gehört:
Delphi-Quellcode:
function Pos(const SubStr, Str: UnicodeString; Offset: Integer): Integer; overload;
{$IFDEF PUREPASCAL}
var
  I, LIterCnt, L, J: Integer;
  PSubStr, PS: PChar;
  LCh: Char;
begin
    // [Delphi - Pascal Implementierung
end;

{$ELSE !PUREPASCAL}
{$IFDEF CPUX86}

asm
       test eax, eax
       jz   @Nil
       test edx, edx
       jz   @Nil
       dec  ecx
       jl   @Nil

       [ ... ]     // jede Menge Assembler-Code

       add  esp, 12
       mov  ecx, [esp]
       or   ecx, [esp+4]
       jz   @NoClear

       mov  ebx, eax                         // was soll das Hin- und Herkopieren der Register?
       mov  esi, edx                         //  ??
       mov  eax, ebx                         //  ??
       mov  edx, esi                         //  ??

@NoClear:
       add  eax, edx
       shr  eax, 1  // divide by 2 to make an index
       pop  ebx
       pop  esi
end;
{$ENDIF CPUX86}
{$ENDIF !PUREPASCAL}
Wozu sind die scheinbar überflüssigen Kopieraktionen gut, zumal ja EBX und ESI anschließend aus dem Stack restauriert weden?
Kann mich einer der hier versammelten Experten mal aufklären, was es damit auf sich hat?

Gruß LP

samso 29. Feb 2020 16:01

AW: Assembler-Rätsel
 
Die Profis sind halt oft im Stress...

Der Code stammt von PosEx aus einem älteren Delphi. Da wurde noch eine Subroutine benötigt. (Die Kommentare stammen natürlich von mir)

Delphi-Quellcode:
       add  esp, 12
       // Ist Bereinigung notwendig?
       mov  ecx, [esp]
       or   ecx, [esp+4]
       jz   @NoClear // Nein, keine Bereinigung nötig

       mov  ebx, eax // eax sichern
       mov  esi, edx // edx sichern
       mov  eax, esp // Parameter laden
       mov  edx, 2    // Parameter laden
       call System.@LStrArrayClr
       mov  eax, ebx // eax wieder herstellen
       mov  edx, esi // edx wieder herstellen

@NoClear:
       add  eax, edx
       add  esp, 8
       pop  ebx
       pop  esi
end;
Bei den neueren Delphis wurden dann die Bereinigung "System.@LStrArrayClr" überflüssig. Der Programmierer hat dann einige Teile die für den Aufruf der Subroutine notwendig waren entfernt, aber das nicht konsequent zu Ende gedacht. Eigentlich hätte er den kompletten Block hinter "add esp,12" weglassen können.

Ähnlich schräg ist der folgende Teil (Delphi 10.1 immer noch function Pos):

Code:
@Exit:
       add  esp, 12
// es folgt überflüssiger Code
@Past:
       mov  eax, [esp]
       or   eax, [esp+4]
       jz   @PastNoClear // Preisfrage: wo landen wir wenn das Z-Flag nicht gesetzt ist?
@PastNoClear:
// ab hier wird es wieder sinnig
       pop  ebx
       pop  esi
@Nil:
       xor  eax, eax
       ret
Vielleicht hätte man sich besser noch mal den Originalcode von Aleksandr Sharahov angesehen - das war wirklich ein Profi!

Maekkelrajter 29. Feb 2020 18:08

AW: Assembler-Rätsel
 
Zitat:

Zitat von samso (Beitrag 1458657)
Ähnlich schräg ist der folgende Teil (Delphi 10.1 immer noch function Pos):

Code:
@Exit:
       add  esp, 12
// es folgt überflüssiger Code
@Past:
       mov  eax, [esp]
       or   eax, [esp+4]
       jz   @PastNoClear // Preisfrage: wo landen wir wenn das Z-Flag nicht gesetzt ist?
@PastNoClear:
// ab hier wird es wieder sinnig
       pop  ebx
       pop  esi
@Nil:
       xor  eax, eax
       ret

Danke für deine erhellenden Ausführungen.
Über die 'Preisfrage' hatte ich allerdings auch schon gegrübelt :shock:
Vielleicht, so argwöhnte ich, steckt ja hinter dem Ganzen höchst raffinierte Compiler- bzw. Assembler-Magie, von der ich keine Ahnung habe?!

Ich bastele nämlich gerade selbst an einer 'Pos'-Funktion, die einen weiteren Parameter entgegennimmt, etwa folgendermaßen
Delphi-Quellcode:
function Pos(const SubStr, Str: UnicodeString; Offset, limit: Integer): Integer;
asm
  // wenn limit <= offset dann Ende mit result = 0
  // wenn limit > length(Str) dann limit = length(Str)
  // nach substr zwischen offset und limit suchen...
end;
Damit sollen Teilbereiche von großen Strings durchsucht werden. Mit
Delphi-Quellcode:
Copy(Str, offset, limit - offset)
einen Teilstring zu erzeugen und dann zu durchsuchen, funktioniert natürlich, ist aber bezüglich der Performance indiskutabel.
Ich habe deshalb nun den Ehrgeiz, sowas doch nochmal in Assembler hinzukriegen 8-)
Schau'n wir mal!

Gruß LP

Maekkelrajter 7. Apr 2020 12:52

AW: Assembler-Rätsel
 
Hallo,

mittlerweile habe ich das gelöst, indem ich den hier gefundenen Original PosEx-Code von Aleksandr Sharahov ein wenig modifiziert habe. Die Änderungen umfassen zusätzlichen Code zur Prüfung des neuen Parameters 'Limit' und die Änderung des 'Notausganges' ab dem Label @NIL. Dort ersetze ich das 'ret' durch einen Sprung zum Funktions-Ende, um den vom Compiler generierten Austritts-Code zu benutzen, der den zusätzlichen Parameter berücksichtigt.

Delphi-Quellcode:
function PosInLimits(const SubStr, Str: RawByteString; Offset: Integer = 1; limit: Integer = 0): Integer;
asm
       test eax, eax
       jz   @Nil
       test edx, edx
       jz   @Nil
       dec  ecx
       jl   @Nil

       push esi
       push ebx

       mov  esi, [edx-4]       //Length(Str)

//***************************************************************  Zusätzlicher Code

       mov  ebx, limit         // limit holen
       test ebx, ebx           // Limit = 0?
       jz   @IgnoreLimit       // limit ignorieren
       cmp  ebx, esi           // limit >= Length(Str)?
       jge  @IgnoreLimit       // limit ignorieren
       mov  esi, ebx           // limit übernehmen anstelle von Length(Str)
@IgnoreLimit:

// **************************************************************  Ende zusätzlicher Code

       mov  ebx, [eax-4]       //Length(Substr)
       sub  esi, ecx           //effective length of Str (= length(str) - offset)
       add  edx, ecx           //addr of the first AnsiChar at starting position
       cmp  esi, ebx
       jl   @Past              //jump if EffectiveLength(Str)<Length(Substr)
       test ebx, ebx
       jle  @Past              //jump if Length(Substr)<=0

       add  esp, -12
       add  ebx, -1             //Length(Substr)-1
       add  esi, edx           //addr of the terminator
       add  edx, ebx           //addr of the last AnsiChar at starting position
       mov  [esp+8], esi       //save addr of the terminator
       add  eax, ebx           //addr of the last AnsiChar of Substr
       sub  ecx, edx           //-@Str[Length(Substr)]
       neg  ebx                //-(Length(Substr)-1)
       mov  [esp+4], ecx       //save -@Str[Length(Substr)]
       mov  [esp], ebx         //save -(Length(Substr)-1)
       movzx ecx, byte ptr [eax] //the last AnsiChar of Substr

@Loop:
       cmp  cl, [edx]
       jz   @Test0
@AfterTest0:
       cmp  cl, [edx+1]
       jz   @TestT
@AfterTestT:
       add  edx, 4
       cmp  edx, [esp+8]
       jb  @Continue
@EndLoop:
       add  edx, -2
       cmp  edx, [esp+8]
       jb   @Loop
@Exit:
       add  esp, 12
@Past:
       pop  ebx
       pop  esi
@Nil:
       xor  eax, eax
       jmp @done                      // ***************************  Modifiziert

@Continue:
       cmp  cl, [edx-2]
       jz   @Test2
       cmp  cl, [edx-1]
       jnz  @Loop
@Test1:
       add  edx, 1
@Test2:
       add  edx, -2
@Test0:
       add  edx, -1
@TestT:
       mov  esi, [esp]
       test esi, esi
       jz   @Found
@AnsiString:
       movzx ebx, word ptr [esi+eax]
       cmp  bx, word ptr [esi+edx+1]
       jnz  @AfterTestT
       cmp  esi, -2
       jge  @Found
       movzx ebx, word ptr [esi+eax+2]
       cmp  bx, word ptr [esi+edx+3]
       jnz  @AfterTestT
       add  esi, 4
       jl   @AnsiString
@Found:
       mov  eax, [esp+4]
       add  edx, 2

       cmp  edx, [esp+8]
       ja   @Exit

       add  esp, 12
       add  eax, edx
       pop  ebx
       pop  esi
@done:
end;
Das Ganze funktioniert anscheinend wie gewünscht. Leider bin ich nicht der große Assembler-Meister, so dass mich die Sorge umtreibt, dass ich doch irgendetwas übersehen habe, was mir irgendwann auf die Füße fällt. Vielleicht könnte irgendein hier mitlesender Assembler-Experte mal einen Blick darauf werfen.
Außerdem eine grundsätzliche Frage: Unter welchen Bedingungen darf ich den Sharahov - Code überhaupt in meinem Programm verwenden? Da ich nicht vorhabe, das Programm oder gar die Quellcodes weiterzugeben, könnte mir das eigentlich völlig wurscht sein. Aber es interessiert mich eben, und die Informationen, die über die verschiedenen Lizenzmodelle im Netz zu finden sind, lassen mich eher ratlos zurück. Kann mir das mal jemand kurz & knackig erläutern?

Gruß LP

TurboMagic 7. Apr 2020 20:06

AW: Assembler-Rätsel
 
Schreib doch einfach Mal ein paar Unit Tests dafür, dann wirst du schon ein Gefühl dafür bekommen ob das soweit alles abdeckt. DUnit/DUnit2 sind deine Freunde...

himitsu 7. Apr 2020 23:47

AW: Assembler-Rätsel
 
Bin auch grade bissl raus, aus'm ASM.
Es gibt ja ein paar Register, die man sichern muss, bzw. da man nicht verändern darf.

Wobei mir grade nicht einfällt wofür EBX war, (glaub Fehlerbehandlung, Stackverwaltung oder so)
im Windows x86 ... für x64 und andere Plattformen bissl anders.

Dank Multiplatform versuch ich wieder möglichst viel in Pascal zu machen, vor allem da eine meiner letzten Assenbler-Optimierungen auf modernen Intel-CPU gegen deren Optimierung arbeitete und mein hochoptimiertes Assembler wesentlich langsamer war, als das was Delphi aus dem Pascal machte.
:stupid:


Hier gibt's auch noch bissl was in der OH zu finden.
http://docwiki.embarcadero.com/RADSt...bly_Code_Index
Zitat:

Zitat von aus Verwendung des integrierten Assemblers
In einer asm-Anweisung muss der Inhalt der Register EDI, ESI, ESP, EBP und EBX erhalten bleiben

Auch dazu, was Delphi in den Pascal-Funktionen am Anfang einfügt und wann/warum.
Zitat:

Zitat von aus Assembler-Prozeduren und -Funktionen
Code:
PUSH  EBP        ;Present if Locals <> 0 or Params <> 0
MOV   EBP,ESP        ;Present if Locals <> 0 or Params <> 0
SUB   ESP,Locals     ;Present if Locals <> 0
; &#8230;
MOV   ESP,EBP        ;Present if Locals <> 0
POP   EBP        ;Present if Locals <> 0 or Params <> 0
RET   Params         ;Always present

Aber mit Conditional-Expressions kommt man IMHO sowieso oft besser.
Zitat:

Delphi-Quellcode:
asm
  ...
end;
{$ELSE !CPUX86}
  {MESSAGE Fatal 'not implemented'}
{$ENDIF !CPUX86}
{$ENDIF !PUREPASCAL}

Delphi-Quellcode:
{$IF Defined(PUREPASCAL)}
begin
  ...
end;
{$ELSEIF Defined(CPUX86)}
asm
  ...
end;
{$ELSE}
  {MESSAGE Fatal 'not implemented'}
{$IFEND}
Hier kann man auch schöner Verschachteln und Zusammenfassen, ohne wirklich in X Ebenen zu verschachteln, wie z.B.
Delphi-Quellcode:
{$IF Defined(AAA) OR Defined(BBB)}
.

Hobbycoder 8. Apr 2020 00:44

AW: Assembler-Rätsel
 
Vor langer, langer Zeit habe ich im Zuge meiner Lehre damals auch mit Assembler programmieren müssen. Das war auf eineM 8008 (kann auch ein anderer gewesen sein), und das ganze mit einem Programmierkoffer (hatte 4 KB Speicher, 8 LEDs, 8 Schalter, 8stellige 7-Segment-Anzeige und einen Hexa-Nummernblock). Zuhause habe ich dann auf meinem C64 noch einige Sachen in Assembler gemacht, nur so aus Spaß.
Das war allerdings noch recht einfach, weil man nahezu alles direkt Addressieren konnte, Bildschirmspeicher, RAM etc.
Danach habe ich mich nie wieder damit beschäftigt.

Mittlerweile hat sich natürlich viel verändert, und mit meinem damaligen Basiswissen kann ich heute nicht mehr viel anfangen, selbst wenn ich meine Unterlagen aus der Lehre wieder ausgrabe.

Daher meine Frage: könnt ihr gute Dokus, Tutorials oder Bücher empfehlen, um sich in dieses Thema wieder etwas einzuarbeiten, wo auch auf die Besonderheiten, die gegenüber der früheren Architektur, heute unter Windows eingegangen wird. Ich will damit natürlich jetzt nicht groß was machen, aber es würde mich schon interessieren in dem Bereich mein Wissen etwas aufzufrischen, und auch mal auszuprobieren, ob ich das heute noch verstehen würde. Wie gesagt, ist halt schon ewig her.

samso 8. Apr 2020 05:48

AW: Assembler-Rätsel
 
@Maekkelrajter: Du solltest den Fall limit<0 noch abfangen.

@Hobbycoder: Nichts für ungut, aber hättest Du nicht ein eigenes Thema aufmachen können?

Maekkelrajter 8. Apr 2020 15:36

AW: Assembler-Rätsel
 
Zitat:

Zitat von samso (Beitrag 1461567)
@Maekkelrajter: Du solltest den Fall limit<0 noch abfangen.

Darauf hatte ich verzichtet, da weiter unten im Code der Vergleich des Substr mit dem zu durchsuchenden Teilstring durchgeführt wird:

Delphi-Quellcode:
     
       mov  ebx, [eax-4] //Length(Substr)
       sub  esi, ecx     //effective length of Str (= length(str) - offset)
       add  edx, ecx     //addr of the first AnsiChar at starting position
       cmp  esi, ebx
       jl   @Past        //jump if EffectiveLength(Str)<Length(Substr)
       
       [...]
@Past:
       pop  ebx
       pop  esi
@Nil:
       xor  eax, eax
       jmp @done
Falls 'EffectiveLength(Str) < Length(Substr)', was bei negativem 'Limit' der Fall wäre, erfolgt der Ausstieg mit Result =0.
Die von dir vorgeschlagene Variante hätte zur Folge, dass auch bei allen Limit-Werten < 0 der limit-Wert einfach ignoriert wird und die Suche dennoch durchgeführt wird.
Deshalb habe ich das ganze Design noch einmal überarbeitet. Die Vorbelegung der Parameter wurde entfernt, ebenso die Null-Prüfung von 'Limit'. Alle Limit-Werte, die kleiner oder gleich Offset sind führen jetzt zu einem Ende mit Result = 0, ebenso wenn Offset kleiner 1 ist.
Für den geplanten speziellen Einsatz dieser Funktion ist das so völlig ausreichend:
Delphi-Quellcode:
     

function PosInLimits(const SubStr, Str: RawByteString; Offset,Limit:Integer): Integer;
asm
       test eax, eax
       jz   @Nil
       test edx, edx
       jz   @Nil
       dec  ecx
       jl   @Nil

       push esi
       push ebx

       mov  esi, [edx-4]       //Length(Str)

       mov  ebx, limit         // limit holen
       cmp  ebx, esi           // limit >= Length(Str)?
       jge  @IgnoreLimit       // limit ignorieren
       mov  esi, ebx           // limit übernehmen anstelle von Length(Str)
@IgnoreLimit:

       mov  ebx, [eax-4]       //Length(Substr)
       sub  esi, ecx           //effective length of Str (= length(str) - offset)
       add  edx, ecx           //addr of the first AnsiChar at starting position
       cmp  esi, ebx
       jl   @Past              //jump if EffectiveLength(Str)<Length(Substr)

       [usw.]
Trotzdem Danke für den Hinweis!

Gruß LP


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:14 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz