c++ - CMPXCHG16B ¿correcto?
x86 cas (4)
Esto no parece exactamente correcto aunque no estoy seguro por qué. El asesoramiento sería genial ya que la documentación para CMPXCHG16B es bastante mínima (no tengo ningún manual de Intel ...)
template<>
inline bool cas(volatile types::uint128_t *src, types::uint128_t cmp, types::uint128_t with)
{
/*
Description:
The CMPXCHG16B instruction compares the 128-bit value in the RDX:RAX and RCX:RBX registers
with a 128-bit memory location. If the values are equal, the zero flag (ZF) is set,
and the RCX:RBX value is copied to the memory location.
Otherwise, the ZF flag is cleared, and the memory value is copied to RDX:RAX.
*/
uint64_t * cmpP = (uint64_t*)&cmp;
uint64_t * withP = (uint64_t*)&with;
unsigned char result = 0;
__asm__ __volatile__ (
"LOCK; CMPXCHG16B %1/n/t"
"SETZ %b0/n/t"
: "=q"(result) /* output */
: "m"(*src), /* input */
//what to compare against
"rax"( ((uint64_t) (cmpP[1])) ), //lower bits
"rdx"( ((uint64_t) (cmpP[0])) ),//upper bits
//what to replace it with if it was equal
"rbx"( ((uint64_t) (withP[1])) ), //lower bits
"rcx"( ((uint64_t) (withP[0]) ) )//upper bits
: "memory", "cc", "rax", "rdx", "rbx","rcx" /* clobbered items */
);
return result;
}
Cuando corro con un ejemplo obtengo 0 cuando debería ser 1. ¿Alguna idea?
Noté algunos problemas,
(1) El problema principal son las restricciones, "rax" no hace lo que parece, más bien el primer caracter "r" permite a gcc usar cualquier registro.
(2) No estoy seguro de cómo su tipo de almacenamiento :: uint128_t, pero asumiendo el pequeño endian estándar para plataformas x86, entonces las palabras clave alta y baja también se intercambian.
(3) Tomar la dirección de algo y enviarlo a otra cosa puede romper las reglas de aliasing. Depende de cómo se definan tus tipos :: uint128_t en cuanto a si esto es un problema (bien si es una estructura de dos uint64_t''s). GCC con -O2 optimizará asumiendo que las reglas de aliasing no son violadas.
(4) * src realmente debería estar marcado como una salida, en lugar de especificar un clobber de memoria. pero esto es realmente más un problema de rendimiento que de corrección. Del mismo modo, rbx y rcx no necesitan ser especificados como triturados.
Aquí hay una versión que funciona,
#include <stdint.h>
namespace types
{
struct uint128_t
{
uint64_t lo;
uint64_t hi;
}
__attribute__ (( __aligned__( 16 ) ));
}
template< class T > inline bool cas( volatile T * src, T cmp, T with );
template<> inline bool cas( volatile types::uint128_t * src, types::uint128_t cmp, types::uint128_t with )
{
bool result;
__asm__ __volatile__
(
"lock cmpxchg16b oword ptr %1/n/t"
"setz %0"
: "=q" ( result )
, "+m" ( *src )
, "+d" ( cmp.hi )
, "+a" ( cmp.lo )
: "c" ( with.hi )
, "b" ( with.lo )
: "cc"
);
return result;
}
int main()
{
using namespace types;
uint128_t test = { 0xdecafbad, 0xfeedbeef };
uint128_t cmp = test;
uint128_t with = { 0x55555555, 0xaaaaaaaa };
return ! cas( & test, cmp, with );
}
- Luke
Toda la documentación de Intel está disponible de forma gratuita: Intel® 64 e IA-32 Architectures Software Developer''s Manuals .
Es bueno tener en cuenta que si está utilizando GCC, no necesita usar el asm en línea para obtener esta instrucción. Puede usar una de las funciones __sync, como:
template<>
inline bool cas(volatile types::uint128_t *src,
types::uint128_t cmp,
types::uint128_t with)
{
return __sync_bool_compare_and_swap(src, cmp, with);
}
Microsoft tiene una función similar para VC ++:
__int64 exchhi = __int64(with >> 64);
__int64 exchlo = (__int64)(with);
return _InterlockedCompareExchange128(a, exchhi, exchlo, &cmp) != 0;
Lo obtuve compilando para g ++ con un ligero cambio (eliminando el ptr de oword en la instrucción cmpxchg16b). Pero no parece sobrescribir la memoria como se requiere, aunque puedo estar equivocado. [Ver actualización] El código se proporciona a continuación seguido de la salida.
#include <stdint.h>
#include <stdio.h>
namespace types
{
struct uint128_t
{
uint64_t lo;
uint64_t hi;
}
__attribute__ (( __aligned__( 16 ) ));
}
template< class T > inline bool cas( volatile T * src, T cmp, T with );
template<> inline bool cas( volatile types::uint128_t * src, types::uint128_t cmp, types::uint128_t with )
{
bool result;
__asm__ __volatile__
(
"lock cmpxchg16b %1/n/t"
"setz %0"
: "=q" ( result )
, "+m" ( *src )
, "+d" ( cmp.hi )
, "+a" ( cmp.lo )
: "c" ( with.hi )
, "b" ( with.lo )
: "cc"
);
return result;
}
void print_dlong(char* address) {
char* byte_array = address;
int i = 0;
while (i < 4) {
printf("%02X",(int)byte_array[i]);
i++;
}
printf("/n");
printf("/n");
}
int main()
{
using namespace types;
uint128_t test = { 0xdecafbad, 0xfeedbeef };
uint128_t cmp = test;
uint128_t with = { 0x55555555, 0xaaaaaaaa };
print_dlong((char*)&test);
bool result = cas( & test, cmp, with );
print_dlong((char*)&test);
return result;
}
Salida
FFFFFFADFFFFFFFBFFFFFFCAFFFFFFDE
55555555
No estoy seguro de que el resultado tenga sentido para mí. Esperaba que el valor anterior fuera algo así como 00000000decafbad00000feedbeef según la definición de estructura. Pero los bytes parecen estar distribuidos dentro de las palabras. ¿Es eso debido a una directiva alineada? Por cierto, la operación CAS parece devolver el valor de retorno correcto. ¿Alguna ayuda para descifrar esto?
Actualización : acabo de depurar con la inspección de memoria con gdb. Allí se muestran los valores correctos. Así que supongo que esto debe ser un problema con mi procedimiento print_dlong. Siéntase libre de corregirlo. Dejo esta respuesta tal como se va a corregir, ya que una versión corregida de esto sería instructiva de la operación cas con resultados impresos.