Frage zu atomarer gcc asm implementation von cmpxchg



  • Hallo allerseits!

    Ich bin derzeit damit beschäftigt, ein paar atomare Operationen zu implementieren, insbesondere cmpxchg (8b unhd 16b). Das ganze geschieht innerhalb einer C++ - Umgebung mit g++ als Compiler. Meine Frage zielt insbesondere auf die zu übergebenden Listen ab, vor allem nachdem ich mal einen Blick in die TBB-Bibliothek von Intel geworfen habe. Befreit man deren Code von den lästigen Makros, erhält man im Grunde für beide Befehle die folgende Implementation:

    static inline intXX_t cmpxchg(volatile void* ptr, intXX_t dest, intXX_t compareTo)
    {
      intXX_t ret;
      __asm__ __volatile__("lock; cmpxchgBB %0, %1
        : "=a"(ret), "=m"(*(volatile intXX_t *)ptr)                /* out */
        : "0"(compareTo), "q"(dest), "m"(*(volatile intXX_t *)ptr) /* in */
        : "memory");                                               /* clobber */
      return ret;
    }
    

    Dabei steht XX für 32 oder 64 und dementsprechend BB für l oder q.

    Nun find ich ein paar Dinge unbefriedigend:

    1. Woher weiß der Compiler, dass er hier die 64Bit R-Register (RDX, RAX, ...) anstelle der 32bit E-Register nehmen muss?

    2. Bei der 32-Bit-Version: der cmpxchgq=cmpxchg8b -Befehl schreibt ggf. (bei Ungleichheit der Operanden) ein Resultat in die Register EDX:EAX und verändert das Zeroflag. Warum aber fehlt in der Clobber-Liste "cc", und wo ist in der Out-Liste der Parameter "d"?

    3a) Wo sind in der Input-Liste die Register ECX und EBX vermerkt, respektive müsste ich diese nicht zumindest in der Clobber-Liste vermerken?
    3b) Wenn meine Argumente wie beispielweise dest int32_t sind, werden dann EDX und ECX automatisch mit dem Adress-Offset der Variablen geladen? Irgendwie müssen sie ja gesetzt sein, da sie ja auch miteinander verglichen werden...
    Mir fehlt hier quasi deren Initialiserung.

    Gruß und vielen Dank für Hilfe,
    G.



  • Kann nur die Frage 3b beantworten, weil grade ausprobiert...

    gilgamash schrieb:

    3b) Wenn meine Argumente wie beispielweise dest int32_t sind, werden dann EDX und ECX automatisch mit dem Adress-Offset der Variablen geladen? Irgendwie müssen sie ja gesetzt sein, da sie ja auch miteinander verglichen werden...

    Habe dazu die Funktion ein wenig angepasst:

    unsigned int cmpxchg(volatile void* ptr, unsigned int dest, unsigned int compareTo) 
    { 
    	unsigned int ret; 
    	__asm__ __volatile__
    	(
    		 "lock cmpxchgl %0, %1"
    		 : "=a"(ret), "=m"(*(volatile unsigned int *)ptr)
    		 : "0"(compareTo), "q"(dest), "m"(*(volatile unsigned int *)ptr)
    		 : "memory"
    	);
    	return ret;
    }
    

    und gebaut mit:

    gcc -c func.c -o func.o -g0 -O2 -fomit-frame-pointer
    

    und disassembliert mit:

    objdump -d func.o
    

    und das kommt raus:

    func.o:     file format elf32-i386
    
    Disassembly of section .text:
    
    00000000 <cmpxchg>:
       0:	8b 54 24 04          	mov    0x4(%esp),%edx
       4:	8b 44 24 0c          	mov    0xc(%esp),%eax
       8:	8b 4c 24 08          	mov    0x8(%esp),%ecx
       c:	f0 0f b1 02          	lock cmpxchg %eax,(%edx)
      10:	c3                   	ret
    

    🤡



  • Kann noch zufällig die Frage 3a beantworten 🙂

    gilgamash schrieb:

    3a) Wo sind in der Input-Liste die Register ECX und EBX vermerkt, respektive müsste ich diese nicht zumindest in der Clobber-Liste vermerken?

    Das Register ebx wird in der Funktion nicht angefasst und das Register ecx darf in der Funktion verändert werden genauso wie eax und edx, alle drei sind "scratch register". Siehe http://www.agner.org/optimize/calling_conventions.pdf , Kapitel "Register usage" auf Seite 10...



  • Salve! Vielen Dank, abc.w. Von Scratch-Registern habe ich bisher noch nicht gehört!
    Das mit EDX und ECX muss ich mal ausprobieren und mir die Registerwerte einfach anzeigen lassen, wird ja aus der Disassemblierung nicht sichtbar, welchen Wert sie haben.

    Gruss und Dank,
    G.


Log in to reply