¿Cómo puedo indicar que se puede usar la memoria *señalada* por un argumento ASM en línea?

9 minutos de lectura

avatar de usuario
abejaencuerda

Considere la siguiente pequeña función:

void foo(int* iptr) {
    iptr[10] = 1;
    __asm__ volatile ("nop"::"r"(iptr):);
    iptr[10] = 2;
}

Usando gcc, esto compila a:

foo:
        nop
        mov     DWORD PTR [rdi+40], 2
        ret

Tenga en cuenta en particular, que la primera escritura a iptr, iptr[10] = 1 no ocurre en absoluto: el asm en línea nop es lo primero en la función, y solo la escritura final de 2 aparece (después de la llamada ASM). Aparentemente, el compilador decide que solo necesita proporcionar una versión actualizada del valor de iptr sí mismopero no la memoria a la que apunta.

Puedo decirle al compilador que la memoria debe estar actualizada con un memory clobber, así:

void foo(int* iptr) {
    iptr[10] = 1;
    __asm__ volatile ("nop"::"r"(iptr):"memory");
    iptr[10] = 2;
}

lo que da como resultado el código esperado:

foo:
        mov     DWORD PTR [rdi+40], 1
        nop
        mov     DWORD PTR [rdi+40], 2
        ret

Sin embargo, esto es muy fuerte de una condición, ya que le dice al compilador todos la memoria tiene que ser escrita. Por ejemplo, en la siguiente función:

void foo2(int* iptr, long* lptr) {
    iptr[10] = 1;
    lptr[20] = 100;
    __asm__ volatile ("nop"::"r"(iptr):);
    iptr[10] = 2;
    lptr[20] = 200;
}

El comportamiento deseado es dejar que el compilador optimice la primera escritura en lptr[20]pero no el primero en escribir iptr[10]. Él "memory" clobber no puede lograr esto porque significa que ambas escrituras deben ocurrir:

foo2:
        mov     DWORD PTR [rdi+40], 1
        mov     QWORD PTR [rsi+160], 100 ; lptr[10] written unecessarily
        nop
        mov     DWORD PTR [rdi+40], 2
        mov     QWORD PTR [rsi+160], 200
        ret

¿Hay alguna forma de decirles a los compiladores que aceptan la sintaxis asm extendida de gcc que la entrada al asm incluye el puntero y cualquier cosa a la que pueda apuntar?

  • @HadiBrais: sí, pero esto es solo un efecto secundario del = lo que significa que el asm podría cambiar el valor del puntero, por lo que gcc tiene que hacer ambas escrituras (ya que pueden escribir en diferentes ubicaciones). Sin embargo, no significa que gcc tenga que escribir antes de llamando al asm en línea (aunque sucede en este caso) y, por lo tanto, no funciona en general (puede construir un ejemplo similar donde falla).

    – BeeOnRope

    3 de junio de 2019 a las 18:28


  • Además, ¿no quieres "+r"? Utilizando "=r" creo que ni siquiera requiere que el compilador pase el valor del puntero al asm?

    – BeeOnRope

    3 jun 2019 a las 18:30


  • Quiere que el “Específicamente, una “m” (*(const float[] ) fptr) le dirá al compilador que todo el objeto de la matriz es una entrada de longitud arbitraria”.

    parte de la respuesta al duplicado vinculado.

    – bufón

  • 3 de junio de 2019 a las 18:32

    Eso es verdad. Pero si la respuesta ayuda, supongo que marcarlo como duplicado está bien. Los futuros visitantes que puedan encontrar su pregunta serán señalados en la dirección correcta. ¿O propones alguna otra solución? Podríamos pedirle a Peter que publique una respuesta aquí también para que obtenga los créditos.

    – bufón


  • 3 de junio de 2019 a las 18:48 @Jester: Creo que nos vendría bien una sesión de preguntas y respuestas canónicas independiente sobre este tema. Había estado planeando escribir uno en algún momento con un ejemplo simple que mostrara la eliminación no deseada de tiendas muertas. Ponerlo en mi bucle sobre la respuesta de matrices fue solo un recurso provisional. Queremos una pregunta con un título que resuma el problema, un cuerpo de pregunta que demuestre el problema y una respuesta que muestre el operando de entrada o salida de memoria ficticia de mejores prácticas actualmente con la sintaxis de conversión a matriz. Eso es bueno para vincular para mostrar a las personas que el problema existe

    en primer lugar.

    – Peter Cordes


3 de junio de 2019 a las 23:07 Eso es correcto; pedir un puntero como entrada a asm en línea hace no implica que la memoria apuntada es también una entrada o una salida o ambas. Con una entrada de registro y una salida de registro, por lo que gcc sabe, su asm simplemente alinea un puntero enmascarando los bits bajos, o le agrega una constante. (En cuyo caso usted querer

para optimizar una tienda muerta). asm volatile La opción sencilla es "memory" y ungolpear1

. La forma más estrecha y específica que estás pidiendo es usar un operando de memoria “ficticio” así comoel puntero en un registro . Su plantilla de asm no hace referencia a este operando (excepto tal vez dentro de un comentario de asm para ver qué eligió el compilador). Le dice al compilador qué memoria Realmente

leer, escribir o leer + escribir. "m" (*(const int (*)[]) iptr)

Entrada de memoria ficticia: "=m" (*(int (*)[]) iptr)o salida: "+m" . O por supuesto

con la misma sintaxis. Esa sintaxis se está convirtiendo en un puntero a matriz y desreferenciando, por lo que la entrada real es una Cformación

. (Si realmente tiene una matriz, no un puntero, no necesita ninguna conversión y solo puede solicitarla como un operando de memoria). []Si deja el tamaño sin especificar con que le dice a GCC que cualquier memoria a la que se acceda en relación con ese puntero es un operando de entrada, salida o entrada/salida. [10] Si utiliza [some_variable]o iptr[size+1] , que le dice al compilador el tamaño específico. Con tamaños de variables de tiempo de ejecución, gcc en la práctica pierde la optimización que es no

parte de la entrada. GCC documenta esto chary por lo tanto lo apoya. Creo que no es una violación de alias estricto si el tipo de elemento de matriz es el mismo que el puntero, o tal vez si es

.
(del manual GCC)

   asm("repne scasb"
    : "=c" (count), "+D" (p)
    : "m" (*(const char (*)[]) p), "0" (-1), "a" (0));

Un ejemplo de x86 donde el argumento de memoria de cadena tiene una longitud desconocida.

Si puede evitar el uso de un golpe temprano en el operando de entrada del puntero, el operando de entrada de memoria ficticia generalmente elegirá un modo de direccionamiento simple usando ese mismo registro. Pero si usa un early-clobber para la corrección estricta de un bucle asm, a veces un operando ficticio hará que gcc desperdicie instrucciones (y un registro adicional) en una dirección base para el operando de memoria. Verifique el asm producción


del compilador.

Fondo:

Este es un error generalizado en los ejemplos de asm en línea que a menudo pasa desapercibido porque el asm está envuelto en una función que no se alinea con ninguna persona que llama que tienta al compilador a reordenar las tiendas para fusionarlas y eliminar las tiendas muertas. La sintaxis de asm en línea de GNU C está diseñada para describir un soltero "m" instrucción al compilador. La intención es que le informe al compilador sobre una entrada de memoria o una salida de memoria con un "=m" o

restricción de operando, y elige el modo de direccionamiento. asm volatile Escribir bucles completos en asm en línea requiere cuidado para asegurarse de que el compilador realmente sepa lo que está pasando (o "memory" suma un

clobber), de lo contrario, corre el riesgo de romperse al cambiar el código circundante o al habilitar la optimización del tiempo de enlace que permite la inserción de archivos cruzados. asm Consulte también Bucle sobre arreglos con ensamblaje en línea para usar un declaración como el buclecuerpo "m" todavía haciendo la lógica de bucle en C. Con real (no ficticio) "=m" y


operandos, el compilador puede desenrollar el ciclo usando desplazamientos en los modos de direccionamiento que elija. "memory" Nota al pie 1: A clobber hace que el compilador trate el asm como una llamada de función no en línea (que podría leer o escribir cualquier memoria, excepto las locales que análisis de escape asm ha probado que no han escapado). El análisis de escape incluye operandos de entrada a la instrucción asm en sí, pero también cualquier variable global o estática en la que cualquier llamada anterior podría haber almacenado punteros. Por lo general, los contadores de bucle local no tienen que derramarse/recargarse alrededor de un "memory" declaración con un

asm volatile golpear.

es necesario asegurarse de que el asm no esté optimizado incluso si sus operandos de salida no se usan (porque necesita que ocurra el efecto secundario no declarado de escribir memoria). volatileO para la memoria que solo lee asm, necesita que asm se ejecute nuevamente si el mismo búfer de entrada contiene datos de entrada diferentes. Sin la instrucción asm podría ser CSEd "memory" fuera de un bucle. (UN clobber hace no asm hacer que el optimizador trate toda la memoria como una entrada al considerar si el

asm declaración incluso necesita ejecutarse). volatilesin operandos de salida es implícitamente , pero es una buena idea hacerlo explícito. (El manual de GCC tiene una sección sobreasm volátil

). asm("... sum an array ..." : "=r"(sum) : "r"(pointer), "r"(end_pointer) : "memory") p.ej

 arr[5] = 1;
 total += asm_sum(arr, len);
 memcpy(arr, foo, len);
 total += asm_sum(arr, len);

tiene un operando de salida por lo que no es implícitamente volátil. Si lo usaste como volatile Sin asm_sum el segundo volatile podría optimizar, asumiendo que el mismo asm con los mismos operandos de entrada (puntero y longitud) producirá la misma salida. Necesitas para cualquier asm que no sea una función pura de sus operandos de entrada explícitos. Si no se optimiza, entonces "memory" la

  • clobber tendrá el efecto deseado de requerir que la memoria esté sincronizada.

    Cuando tenga tiempo más tarde, ampliaré esto con un ejemplo de Godbolt.

    – Peter Cordes

  • 4 de junio de 2019 a las 0:07 FYI: Golpear un número específico de bytes no siempre funciona tan bien como cabría esperar:

    gcc.gnu.org/bugzilla/show_bug.cgi?id=63900

    –David Wohlferd

  • 4 de junio de 2019 a las 1:40 char* @DavidWohlferd: gracias, no me había dado cuenta de que podría afectar a otras matrices. Pensé que el peor de los casos era que trata una longitud variable en tiempo de ejecución como infinita; No había probado con una segunda matriz. ¿Es eso tal vez por

    ser capaz de alias cualquier cosa? lo probare…

    – Peter Cordes


  • 4 de junio de 2019 a las 1:58

    Como puede ver en los otros comentarios allí, no tengo una comprensión clara de todos los mecanismos internos aquí (¿modo BLK?). Sin embargo, otros parecen ver los mismos resultados. Mi conclusión fue simplemente que, como ocurre con la mayoría de los intentos de engañar al compilador, este no siempre funciona. Bueno, en realidad “funciona” en el sentido de que siempre produce la respuesta correcta, solo que no siempre es totalmente óptima.

    –David Wohlferd

  • 4 de junio de 2019 a las 2:13 @PeterCordes Citar blogs de desarrolladores o hilos en la lista de correo de GCC que discutan esta función sería suficiente. Tenga en cuenta que código relevante en GCC

    no es trivial, por ejemplo, fusiona registros solo cuando las clases de registro se consideran “pequeñas” (cualquiera que sea el objetivo particular que elija que signifique).

    – yugr


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad