2010-10-10 14 views
15

Tengo dificultades para entender las restricciones de función en el ensamblaje en línea de GCC (x86). Tengo read the manual, lo que explica exactamente lo que hace cada restricción. El problema es que, aunque entiendo lo que hace cada restricción, entiendo muy poco por qué usarías una restricción sobre otra, o cuáles podrían ser las implicaciones.Ensamblaje en línea de GCC: restricciones

Me doy cuenta de que este es un tema muy amplio, por lo que un pequeño ejemplo debería ayudar a reducir el enfoque. La siguiente es una rutina de asm simple que solo agrega dos números. Si se produce un desbordamiento de enteros, escribe un valor de 1 en una variable C de salida.

int32_t a = 10, b = 5; 
int32_t c = 0; // overflow flag 

__asm__ 
(
    "addl %2,%3;"  // Do a + b (the result goes into b) 
    "jno 0f;"   // Jump ahead if an overflow occurred 
    "movl $1, %1;"  // Copy 1 into c 
    "0:"     // We're done. 

    :"=r"(b), "=m"(c) // Output list 
    :"r"(a), "0"(b)  // Input list 
); 

Ahora bien, esto funciona bien, excepto que tenía que jugar de forma arbitraria con las restricciones hasta que llegué que funcione correctamente. Originalmente, he utilizado las siguientes limitaciones:

:"=r"(b), "=m"(c) // Output list 
    :"r"(a), "m"(b)  // Input list 

Tenga en cuenta que en lugar de un "0", que utilizo una restricción "m" para b. Esto tuvo un extraño efecto secundario en el que si compilé con indicadores de optimización y llamé a la función dos veces, por alguna razón el resultado de la operación de adición también se almacenará en c. Eventualmente leí sobre "matching constraints", que le permite especificar que una variable se utilizará como un operando de entrada y salida. Cuando cambié "m"(b) a "0"(b) funcionó.

Pero realmente no entiendo por qué usaría una restricción sobre otra. Quiero decir, sí, entiendo que "r" significa que la variable debe estar en un registro y "m" significa que debe estar en la memoria, pero no Realmente entiendo cuáles son las implicaciones de elegir una sobre otra, o por qué la operación de adición no funciona correctamente si elijo una determinada combinación de restricciones.

Preguntas: 1) En el código de ejemplo anterior, ¿por qué la restricción "m" en b causa que se escriba c? 2) ¿Hay algún tutorial o recurso en línea que entre en más detalles sobre las restricciones?

Respuesta

13

He aquí un ejemplo para ilustrar mejor por qué debería elegir limitaciones cuidadosamente (la misma función que el suyo, pero tal vez escrito un poco más sucinta):

bool add_and_check_overflow(int32_t& a, int32_t b) 
{ 
    bool result; 
    __asm__("addl %2, %1; seto %b0" 
      : "=q" (result), "+g" (a) 
      : "r" (b)); 
    return result; 
} 

Por lo tanto, las restricciones utilizadas fueron: q, r, y g.

  • q significa que sólo eax, ecx, edx, o ebx podrían seleccionarse. Esto se debe a que las instrucciones set* deben escribir en un registro direccionable de 8 bits (al, ah, ...). El uso de b en el %b0 significa, use la porción más baja de 8 bits (al, cl, ...).
  • Para la mayoría de las instrucciones de dos operandos, al menos uno de los operandos debe ser un registro. Por lo tanto, no use m o g para ambos; use r para al menos uno de los operandos.
  • Para el operando final, no importa si es registro o memoria, entonces use g (general).

En el ejemplo anterior, he optado por utilizar g (en lugar de r) para a porque las referencias son generalmente implementados como punteros de memoria, así que usar una restricción r habría requerido copiar el referente a un registro en primer lugar, y luego copiando de vuelta. Usando g, el referente podría actualizarse directamente.


En cuanto a por qué su versión original sobrescribía su c con el valor de la adición, eso es porque ha especificado =m en la ranura de salida, en lugar de (digamos) +m; eso significa que el compilador puede reutilizar la misma ubicación de memoria para entrada y salida.

En su caso, eso significa dos resultados (ya que la misma posición de memoria se utiliza para b y c):

  • La adición qué no desbordamiento: entonces, c obtuve sobrescribe con el valor de b (el resultado de la adición).
  • La adición rebasó: luego, c se convirtió en 1 (y b también podría convertirse en 1, dependiendo de cómo se generó el código).
+0

Gracias, esta es una respuesta excelente. Solo una aclaración: ¿por qué el modificador de restricción '=' (solo escritura) le da al compilador el derecho de reutilizar la misma ubicación de memoria, a pesar de que 'b' y' c' son variables diferentes con diferentes ubicaciones en la memoria? – Channel72

+0

@ Channel72: "aunque' b' y 'c' son variables diferentes con diferentes ubicaciones en la memoria" --- en realidad es una suposición importante, una que a menudo no se aplica. Si 'b' y' c' son variables locales, es probable que ambos estén respaldados por registros, en lugar de una ubicación de memoria. En ese caso, la ubicación de la memoria es simplemente un lugar de espera temporal que está configurado exclusivamente para acomodar su restricción 'm' --- en cuyo caso,' b' y 'c' podrían usar la misma ubicación temporal. –

+0

Ahora, si 'b' y' c' estuvieran realmente respaldados por ubicaciones de memoria, entonces estaríamos acertados ya que normalmente no deberían superponerse en absoluto. Y, si uno está respaldado por memoria y el otro respaldado por registro ... entonces cualquiera de esos escenarios es posible. –

Cuestiones relacionadas