Wie wird das Registerflagbei CMPSD gesetzt?



  • Hallo,

    ich habe folgenden Code, der zwei Speicherstellen (mit angegebener Größe) vergleicht.

    function MemCompare(var lho, rho; size: DWORD): Integer; assembler;
    asm
    	{ EAX -> lho          		result = 0		equal
        EDX -> rho              result = 1		lho > rho
        ECS -> size             result = -1		lho < rho	}
    
      push	edi
      push	esi
      push	ebx
    
      mov		edi, eax
      mov		esi, edx
    
      add		edi, ecx
      add		esi, ecx		// offset
    
      xor		eax, eax 		// null result
      mov		ebx, ecx		// save value
    
      shr		ecx, 2			// look for dword align
      jz		@@Rest
    
      std								// set direction flag to decrement
      repe	cmpsd
      ja		@@Above
      jb		@@Below
    
    @@Rest:
    	mov		ecx, ebx
      and		ecx, 3
    
      repe	cmpsb
    	jz		@@Exit
      jb		@@Below
    
    @@Above:						// lho < rho
    	dec		eax
      jmp		@@Exit
    
    @@Below:    				// lho > rho
    	inc		eax
    
    @@Exit:
    	cld
    
      pop		ebx
      pop		esi
      pop		edi
    end; (* of MemCompare *)
    

    Leider funktinoiert es nicht ganz. Wenn beide Werte gleich sind, dann gibt er 0 zurück (korrekt), aber ansonsten springt er immer zu below ... warum?

    EDIT: Da war ich mal wieder zu hastig. Ist natürlich ein Fehler drin gewesen:

    function MemCompare(const lho, rho; size: DWORD): Integer; assembler;
      asm
        { EAX -> lho          		result = 0		equal
          EDX -> rho              result = 1		lho > rho
          ECS -> size             result = -1		lho < rho	}
    
        push	edi
        push	esi
        push	ebx
    
        mov		edi, eax
        mov		esi, edx
    
        xor		eax, eax 		// null result
        mov		ebx, ecx		// save value
    
        shr		ecx, 2			// look for dword align
        jz		@@Rest
    
        mov		edx, ebx
        sub		edx, 4
        add		edi, edx    // position on last dword
        add		esi, edx
      {
        mov		eax, dword ptr [esi]
        mov		eax, dword ptr [edi]
    
        sub		esi, 4
        sub		edi, 4
        mov		eax, dword ptr [esi]
        mov		eax, dword ptr [edi]
      }
        std								// set direction flag to decrement
        repe	cmpsd
        ja		@@Above
        jb		@@Below
    
      @@Rest:
        mov		ecx, ebx
        and		ecx, 3
    
        repe	cmpsb
        jz		@@Exit
        jb		@@Below
    
      @@Above:						// lho < rho
        dec		eax
        jmp		@@Exit
    
      @@Below:    				// lho > rho
        inc		eax
    
      @@Exit:
        cld
    
        pop		ebx
        pop		esi
        pop		edi
      end; (* of MemCompare *)
    (* ------------------------------------------------------------------------------------ *)
    

  • Mod

    Wieso erfolgt der Vergleich bei den dword-Schritten von hinten, nicht aber bei den Einzelbytevergleichen, wenn size<4 ?



  • Und warum asiatischer Still? 😉



  • camper schrieb:

    Wieso erfolgt der Vergleich bei den dword-Schritten von hinten, nicht aber bei den Einzelbytevergleichen, wenn size<4 ?

    Ja, den fehler habe ich kurz nach dem posten auch bemerkt. Ist natürlich Quatsch. Gründsätzlich geht es darum, dass ich natürlich das höchstwertige Byte zu erst vergleichen möchte.

    @abc.w
    asiatischer Stil?



  • FrEEzE2046 schrieb:

    @abc.w
    asiatischer Stil?

    Ja, wegen grosser Abstände

    mov        edi, eax
    

    Und so kann man Deine Funktion "nur" von oben nach unten lesen und das ist unleserlich (für mich) 🙂


  • Mod

    Funktioniert denn so eine einfache Variante?

    function MemCompare(const lho, rho; size: DWORD): Integer; assembler;
      asm
        { EAX -> lho              result = -1        lho < rho
          EDX -> rho              result = 0         lho == rho
          ECX -> size             result = 1         lho > rho    }
    
        push    edi
        push    esi
    
        lea     esi, [eax+ecx-1]
        lea     edi, [edx+ecx-1]
        std
    
        rep cmpsb
    
        cld
        seta    eax
        cmovb   eax, -1
    
        pop     esi
        pop     edi
      end;
    


  • camper schrieb:

    Funktioniert denn so eine einfache Variante?

    Es funktioniert auch so eine einfache Variante - wobei mir folgende Zeilen schleierhaft sind:

    lea     esi, [eax+ecx-1]
        lea     edi, [edx+ecx-1]
    

    Im EAX bzw. EDX Register steht bereits die Adresse des Strings ... mit der Adresse des Pointers selbst kann ich nicht viel anfangen.

    Ansonsten ist es halt einfach schneller zunächst die DWORDs zu überprüfen (gerade bei größeren Werten) würde ich sagen.


  • Mod

    FrEEzE2046 schrieb:

    Ansonsten ist es halt einfach schneller zunächst die DWORDs zu überprüfen (gerade bei größeren Werten) würde ich sagen.

    Stimmt, allerdings muss der Wert von esi/edi beim Übergang von cmpsd auf cmpsb entsprechend angepasst werden. Da die Fehlerbeschreibung nicht ganz zutreffend ist, ist das bisher vermutlich niemandem aufgefallen:

    function MemCompare(const lho, rho; size: DWORD): Integer; assembler;
      asm
        { EAX -> lho              result = -1        lho < rho
          EDX -> rho              result = 0         lho == rho
          ECX -> size             result = 1         lho > rho    }
    
        push    edi
        push    esi
    
        lea     esi, [eax+ecx-1]
        lea     edi, [edx+ecx-1]
        std
    
        mov     eax, ecx
        shr     eax, 2
        and     ecx, 3
    
        repe cmpsb
        jne     @@Exit
    
        mov     ecx, eax
        lea     esi, [esi-3]
        lea     edi, [edi-3]
    
        repe cmpsd
    
    @@Exit:
        cld
        seta    eax
        cmovb   eax, -1
    
        pop     esi
        pop     edi
      end;
    

    Da es wahrscheinlicher sein dürfte, dass der Stringanfang dword ausgerichtet ist, als dass das beim Stringende der Fall ist, sollte die byte-weisen Vergleiche zuerst stattfinden, damit die Vergleichsadressen für den dword-Vergleich ausgerichtet sind.

    Edit: kleine Änderung, damit es auch mit size<4 klappt (Flags!)



  • camper schrieb:

    Da es wahrscheinlicher sein dürfte, dass der Stringanfang dword ausgerichtet ist, als dass das beim Stringende der Fall ist, sollte die byte-weisen Vergleiche zuerst stattfinden, damit die Vergleichsadressen für den dword-Vergleich ausgerichtet sind.

    Ich gebe dir recht im Bezug darauf, dass es wahrscheinlicher sein dürfte, dass der Anfang DWORD aligned ist, als das Ende - bei einem String, in diesem Fall jedoch nicht.

    Warum du jedoch vom edi und esi Register 3 abziehst verstehe ich nicht.
    Erst einmal sollte dir doch auffallen, dass ich von hinten Vergleiche und danach entscheide ob lho oder rho größer, kleiner, gleich ist.
    Wenn ich Strings vergleichen würde (also Zeichenketten), dann wäre es ziemlicher Schwachsinn am least significant byte fest zu machen welcher größer ist oder 😉

    Dieser byteweise Vergleich ist vorallem für ganzzahlige Typen gedacht gewesen (oder Vektoren davon). Da wir die Werte in little endian Reihenfolge vorfinden, ist das "hintere" dword eines (z.B.) long long nun mal das signifikantere für die Feststellung größer, kleiner, gleich.
    Daher ist es - aus meiner Sicht - auch richtiger "zuerst" die DWORDs zu vergleichen und dann die Bytes.

    Desweiteren sehe ich kein Problem bei der Verwendung meiens Codes. Nenn bitte ein Beispiel, bei dem der Vergleich "fehlschlagen" würde. Ich will damit nicht sagen, dass du unrecht hast, aber ich sehe es wirklich nicht.


  • Mod

    Betrache:
    lho = "000aaa"
    rho = "000aaa"
    size = 6
    In deinem Code: beim ersten Vergleich betrachtest du die letzten vier Zeichen:

    000aaa
    000aaa
      ^      esi/edi
    

    Am Ende des Vergleiches werden esi/edi erniedrigt um die Größe des Wergleichswortes, also 4:

    000aaa
      000aaa
    ^         esi/edi
    

    Der folgende byte-weise Vergleich vergleicht also gar nicht mehr Bytes, die zu den Strings gehören, denn es wurden 3 Byte übersprungen.

    Analog werden, wenn, wie in meinem Code, erst der byte-weise Vergleich erfolgt, ohne Anpassung 3 bytes des letzten dwords doppelt verglichen. Die würden dann am Ende fehlen.



  • BTW:

    cmovb   eax, -1
    

    cmovxx erlaubt keine immediate's - allgemein: CMOVcc r,r/m


  • Mod

    stimmt, dann eben

    sbb eax, eax
    


  • cld 
        seta    eax 
        cmovb   eax, -1
    

    - 'seta eax' gibt es ebenfalls nicht: seta al oder ah.
    - sbb eax,eax würde für den Fall !CARRY das gesetzte bit in al/ah löschen.
    =>

    mov eax,0
    	seta al
    	sbb eax,0
    


  • cmovb schrieb:

    cld 
        seta    eax 
        cmovb   eax, -1
    

    - 'seta eax' gibt es ebenfalls nicht: seta al oder ah.
    - sbb eax,eax würde für den Fall !CARRY das gesetzte bit in al/ah löschen.
    =>

    mov eax,0
    	seta al
    	sbb eax,0
    

    Warum diese Reihenfolge. Hier wird immer -1 rauskommen oder etwa nicht?

    Ich brauche jetzt auch noch eine Funktion zum String-Vergleich. Grundsätzlich kann ich da ja genau so verfahren (abgesehen davon, dass ich eine "logisches" big endian byte order habe.
    Was mir allerdings Probleme macht ist, wenn die Strings nicht alle Chars auf Upper/Lower Case haben, sondern Groß-/Kleinschreibung enthalten. Wie kann ich das am besten lösen?

    Ich will keine Reihenfolge dieser Art haben:
    ABC
    Bertha
    Omega
    alpha


  • Mod

    FrEEzE2046 schrieb:

    Warum diese Reihenfolge. Hier wird immer -1 rauskommen oder etwa nicht?

    Warum? Nochmal drüber nachdenken, wie jede Instruktion die Flags beeinflusst.

    Eigentlich hatte ich gehofft, eine Instruktion einsparen zu können - eine Instruktion pro mögliches Ergebnis ist nicht besonders schön. Geht wohl doch nicht. Das mov könnte man noch durch xor ersetzen

    push    edi
        push    esi
    
        lea     esi, [eax+ecx-1]
        lea     edi, [edx+ecx-1]
        std
    
        mov     edx, ecx
        shr     edx, 2
        and     ecx, 3
        xor     eax, eax
    
        repe cmpsb
        jne     @@Exit
    
        mov     ecx, edx
        lea     esi, [esi-3]
        lea     edi, [edi-3]
    
        repe cmpsd
    
    @@Exit:
        cld
        seta    al
        sbb     eax, 0
    
        pop     esi
        pop     edi
    


  • mov eax,0
    	seta al
    	sbb eax,0
    

    1. Zeile: mov eax, 0: Schreibet 0 in eax
    2. Zeile: seta (set byte on condition / if above) al: Wenn höher dann 1
    3. Zeile: sbb (subtraction with borrow), 0: Wenn Carry-Flag gesetzt um 1 weiter dekrementieren.

    mmmh...


  • Mod

    FrEEzE2046 schrieb:

    Was mir allerdings Probleme macht ist, wenn die Strings nicht alle Chars auf Upper/Lower Case haben, sondern Groß-/Kleinschreibung enthalten. Wie kann ich das am besten lösen?

    Es gibt mehrere Möglichkeiten, z.B. kann erst einmal ein
    repe cmps
    durchführen, und bei Ungleichheit zusätzlich prüfen, ob die Ungleichheit nur auf Unterschiede zwischen Groß und Klein zurückzuführen ist, und den Vergleich dann ggf. fortsetzen. Auch hier ist die Optimierung per cmpsd denkbar.



  • camper schrieb:

    durchführen, und bei Ungleichheit zusätzlich prüfen, ob die Ungleichheit nur auf Unterschiede zwischen Groß und Klein zurückzuführen ist, und den Vergleich dann ggf. fortsetzen. Auch hier ist die Optimierung per cmpsd denkbar.

    Danke für deine Antwort.

    Was mir aufgefallen ist:

    seta  al
    sbb   eax, 0
    

    liefert die Ergebnisse genau falsch rum^^. Daher meine bedenken.


  • Mod

    FrEEzE2046 schrieb:

    seta  al
    sbb   eax, 0
    

    liefert die Ergebnisse genau falsch rum^^.

    esi/edi wurden auch vertauscht.



  • Ich habe dazu noch mal eine generelle Frage:

    Wo ist eigentlich der Unterschied zwischen:

    lea edi, [eax+ecx-1]
    

    und

    mov edi, eax
    add edi, ecx
    dec edi
    

    Muss der Prozessor nicht immer die Schritte von "2" durchführen? Im Endeffekt ist "1" doch nur eine kürzere Schreibweise oder bringt dass tatsächlich auch sonst noch was?

    EDIT:
    Warum sind bei mir die Ergebniswerte vertauscht (1 u. -1)? Ich mach doch nicht viel anders als du:

    function __MemCompare__LE_DF__(const lho, rho; size: DWORD): Integer; assembler;
      asm
        { EAX -> lho          		result = 0		equal
          EDX -> rho              result = 1		lho < rho
          ECX -> size             result = -1		lho > rho	}
    
        push	edi
        push	esi
        push	ebx
    
        std								// set direction flag to decrement
        mov		edi, eax
        mov		esi, edx
    
        xor		eax, eax 		// null result
        mov		ebx, ecx		// save value
    
        shr		ecx, 2			// look for dword align
        jz		@@Rest
    
        mov		edx, ebx
        sub		edx, 4
        add		edi, edx    // position on last dword
        add		esi, edx
    
        repe	cmpsd
        jne		@@Exit
    
      @@Rest:
        mov		ecx, ebx
        and		ecx, 3
        jz		@@Exit
    
        add		edi, ecx   	// byte offset
        add		esi, ecx
    
        repe	cmpsb
    
      @@Exit:
      	cld
        seta  al
        sbb   eax, 0
    
        pop		ebx
        pop		esi
        pop		edi
      end; (* of MemCompare *)
    (* ------------------------------------------------------------------------------------ *)
    

Anmelden zum Antworten