asm - use assembler in c
¿Qué hace__asm____volatile__ en C? (3)
Miré en algún código C de
http://www.mcs.anl.gov/~kazutomo/rdtsc.html
Usan cosas como "en línea ", " asm ", etc. como las siguientes:
código1:
static __inline__ tick gettick (void) {
unsigned a, d;
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) );
return (((tick)a) | (((tick)d) << 32));
}
code2:
volatile int __attribute__((noinline)) foo2 (int a0, int a1) {
__asm__ __volatile__ ("");
}
Me preguntaba qué hacen el código1 y el código2.
El atributo __asm__
especifica el nombre que se utilizará en el código de ensamblador para la función o variable.
El calificador __volatile__
, generalmente utilizado en la computación en tiempo real de sistemas integrados, soluciona un problema con las pruebas de compilación del status register
para el bit ERROR
o READY
causa problemas durante la optimización. __volatile__
se introdujo como una forma de decirle al compilador que el objeto está sujeto a cambios rápidos y para obligar a que cada referencia del objeto sea una referencia genuina.
El modificador __volatile__
en un bloque __asm__
obliga al optimizador del compilador a ejecutar el código tal como está. Sin él, el optimizador puede pensar que puede eliminarse directamente o sacarse de un bucle y almacenarse en caché.
Esto es útil para la instrucción rdtsc
así:
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )
Esto no requiere dependencias, por lo que el compilador podría suponer que el valor puede almacenarse en caché. Volatile se utiliza para forzarlo a leer una nueva marca de tiempo.
Cuando se usa solo, así:
__asm__ __volatile__ ("")
En realidad, no ejecutará nada. Sin embargo, puede ampliar esto para obtener una barrera de memoria en tiempo de compilación que no permita reordenar las instrucciones de acceso a la memoria:
__asm__ __volatile__ ("":::"memory")
La instrucción rdtsc
es un buen ejemplo de volátil. rdtsc
se usa generalmente cuando necesita rdtsc
tiempo que tardan algunas instrucciones en ejecutarse. Imagina un código como este, donde quieres r2
la ejecución de r1
y r2
:
__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )
Aquí, el compilador puede almacenar en caché la marca de tiempo, y una salida válida podría mostrar que cada línea tomó exactamente 0 relojes para ejecutarse. Obviamente, esto no es lo que quieres, por lo que debes introducir __volatile__
para evitar el almacenamiento en caché:
__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0))
r1 = x1 + y1;
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1))
r2 = x2 + y2;
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2))
Ahora obtendrá una nueva marca de tiempo cada vez, pero todavía tiene el problema de que tanto el compilador como la CPU pueden reordenar todas estas declaraciones. Podría terminar ejecutando los bloques asm después de que r1 y r2 ya hayan sido calculados. Para evitar esto, agregaría algunas barreras que fuerzan la serialización:
__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory")
r1 = x1 + y1;
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory")
r2 = x2 + y2;
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory")
Tenga en cuenta la instrucción mfence
aquí, que impone una barrera del lado de la CPU, y el especificador de "memoria" en el bloque volátil que impone una barrera de tiempo de compilación. En las CPU modernas, puede reemplazar mfence:rdtsc
con rdtscp
por algo más eficiente.
asm
es para incluir código ensamblador nativo en el código fuente C. P.ej
int a = 2;
asm("mov a, 3");
printf("%i", a); // will print 3
Los compiladores tienen diferentes variantes de esto. __asm__
debería ser, quizás con algunas diferencias específicas del compilador.
volatile
significa que la variable puede modificarse desde afuera (también conocido como no por el programa C). Por ejemplo, cuando se programa un microcontrolador en el que la dirección de memoria 0x0000x1234
se asigna a alguna interfaz específica del dispositivo (es decir, cuando se accede a la codificación del GameBoy, se accede a botones / pantalla / etc.).
volatile std::uint8_t* const button1 = 0x00001111;
Esto desactiva las optimizaciones del compilador que dependen de que *button1
no cambie a menos que el código lo cambie.
También se utiliza en la programación de subprocesos múltiples (¿ya no es necesario?) Donde una variable podría ser modificada por otro hilo.
inline
es una sugerencia para el compilador de llamadas "en línea" a una función.
inline int f(int a) {
return a + 1
}
int a;
int b = f(a);
Esto no debe compilarse en una llamada de función a f
sino a int b = a + 1
. Como si f
donde una macro. Los compiladores realizan esta optimización de forma automática en función del uso / contenido de la función. __inline__
en este ejemplo podría tener un significado más específico.
__attribute__((noinline))
(sintaxis específica de GCC) previene que una función sea inlineada.