Pregunta

Mi código

const int howmany = 5046;
char buffer[howmany];
    asm("lea     buffer,%esi"); //Get the address of buffer
    asm("mov     howmany,%ebx");         //Set the loop number
    asm("buf_loop:");                      //Lable for beginning of loop
    asm("movb     (%esi),%al");             //Copy buffer[x] to al
    asm("inc     %esi");                   //Increment buffer address
    asm("dec     %ebx");                   //Decrement loop count
    asm("jnz     buf_loop");              //jump to buf_loop if(ebx>0)

Mi Problema

Estoy utilizando el compilador gcc. Por alguna razón mi memoria intermedia / howmany variables que no están definidos en los ojos de mi asm. No estoy seguro de por qué. Sólo quiero mover la dirección de comienzo de mi matriz del separador en el registro ESI, bucle 'howmany' veces, mientras que la copia de cada elemento en el registro al.

¿Fue útil?

Solución

¿Está utilizando el ensamblador en línea en gcc? (Si no es así, ¿en qué otro compilador de C ++, exactamente?)

Si gcc, ver los detalles aquí y, en particular este ejemplo:

    asm ("leal (%1,%1,4), %0"
         : "=r" (five_times_x)
         : "r" (x) 
         );

%0 y %1 se refieren a las variables de nivel C, y que están enumerados específicamente como el segundo (para las salidas) y tercero (para entradas) parámetros para asm. En su ejemplo que sólo tienen "entradas" por lo que tendría un segundo operando vacío (tradicionalmente uno usa un comentario después de que el colon, como /* no output registers */, para indicar que más explícitamente).

Otros consejos

La parte que declara una matriz como la

int howmany = 5046;
char buffer[howmany];

no es válido C ++. En C ++ es imposible declarar una matriz que tiene "variable" o de tiempo de ejecución tamaño. En C ++ array Declaraciones El tamaño es siempre una constante de tiempo de compilación.

Si su compilador permite que esta declaración de matriz, significa que se implementa como una extensión. En ese caso, usted tiene que hacer su propia investigación para averiguar cómo se implementa un tiempo de ejecución tal variedad de tamaño internamente. Me imagino que buffer internamente se implementa como un puntero , no como una verdadera matriz. Si mi suposición es correcta y es realmente un puntero, entonces la forma correcta de cargar la dirección de la matriz en esi podría ser

mov buffer,%esi

y no un lea, como en el código. lea sólo funcionará con matrices "normales" de tiempo de compilación de tamaño, pero no con matrices de tamaño en tiempo de ejecución.

Otra cuestión es si realmente se necesita una matriz de tamaño en tiempo de ejecución en el código. Podría ser que usted acaba de hacer por lo que por error? Si basta con cambiar la declaración howmany a

const int howmany = 5046;

la matriz se convertirá en una matriz "normal" C ++ y su código podría empezar a trabajar como es (es decir, con lea).

Todas esas instrucciones asm necesitan estar en el mismo comunicado asm si usted quiere estar seguro de que son contiguas (sin el código generado por el compilador entre ellos), y tiene que declarar de entrada / operandos de salida / clobber o se le paso en un registro del compilador.

No se puede utilizar lea o mov a / de un nombre de variable C (a excepción de los símbolos globales / estática que en realidad se definen en la salida del compilador ASM, pero incluso entonces por lo general no debería ).

En lugar de utilizar las instrucciones mov a configurar las entradas, pedir al compilador que lo haga por usted mediante restricciones operando de entrada. Si la primera o la última instrucción de una declaración en línea asm C de GNU, por lo general significa que lo estás haciendo mal y escribir código ineficiente.

Y por cierto, GNU C ++ permite matrices de longitud variable de tipo C99, por lo que se permite howmany a ser no const e incluso establecer de una manera que no optimiza distancia a una constante. Cualquier compilador que puede compilar al estilo GNU asm en línea también apoyará arreglos de longitud variable.


Cómo escribir su bucle correctamente

Si esto parece demasiado complicado, a continuación, https://gcc.gnu.org/wiki/DontUseInlineAsm . Escribir una función independiente en asm lo que sólo se puede aprender ASM en lugar de tener que aprender también sobre gcc y su interfaz de línea-asm compleja pero de gran alcance. Es, básicamente, tiene que saber y entender asm compiladores usarlo correctamente (con las restricciones adecuadas para evitar que se rompan cuando se habilita la optimización).

Tenga en cuenta el uso de operandos nombradas como %[ptr] en lugar de %2 o %%ebx. Dejar que el compilador elegir que registra a utilizar es normalmente una buena cosa, pero para x86 Hay cartas que no sean "r" se pueden utilizar, como "=a" para rax / EAX / hacha / Al específicamente. Ver https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm .html , y también otros eslabones de la inline-conjunto de etiqueta wiki .

También utilicé buf_loop%=: para anexar un número único de la etiqueta, por lo que si los clones optimizador de la función o Inlines TI múltiples lugares, el archivo sigue a montar.

Fuente + compilador de salida asm en el Godbolt compilador explorador .

void ext(char *);

int foo(void) 
{
    int howmany = 5046;   // could be a function arg
    char buffer[howmany];
    //ext(buffer);

    const char *bufptr = buffer;  // copy the pointer to a C var we can use as a read-write operand
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       :   [res]"=a"(result)      // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , [ptr] "+r" (bufptr)
       : // no input-only operands
       : "memory"   // we read memory that isn't an input operand, only pointed to by inputs
    );
    return result;
}

He utilizado %%al como un ejemplo de la forma de escribir el registro de nombres de forma explícita: Asm extendido (con operandos) necesita una doble % para obtener una % literal en la salida asm. También es posible usar %[res] o %0 y dejar que el compilador %al sustituto en su salida asm. (Y entonces tendría ninguna razón para utilizar una limitación específica del registro a menos que quería aprovechar cbw o lodsb o algo por el estilo.) result es unsigned char, por lo que el compilador recoger un registro de bytes por ello. Si desea que el byte bajo de un operando más amplio, se podría utilizar %b[count] por ejemplo.

Este utiliza un clobber "memory", que es ineficiente . No es necesario que el compilador derrame todo a la memoria, sólo para asegurarse de que el contenido de buffer[] en la memoria coincide con el estado de la máquina abstracta C. (Esto es no garantizada pasando un puntero a ella en un registro).

salida gcc7.2 -O3:

    pushq   %rbp
    movl    $5046, %edx
    movq    %rsp, %rbp
    subq    $5056, %rsp
    movq    %rsp, %rcx         # compiler-emitted to satisfy our "+r" constraint for bufptr
    # start of the inline-asm block
    buf_loop18:  
       movb     (%rcx), %al 
       inc     %rcx        
       dec     %edx      
       jnz     buf_loop      
    # end of the inline-asm block

    movzbl  %al, %eax
    leave
    ret

Sin clobber memoria o restricción de entrada, aparece leave antes bloque asm en línea, liberar esa memoria de pila antes de la ASM en línea utiliza el puntero ahora rancio. Una señal-manejador de funcionamiento en el momento equivocado podría darle una paliza a él.


Una manera más eficiente es utilizar un operando de memoria ficticia que indica al compilador que toda la matriz es una entrada de la memoria de sólo lectura a la declaración asm Ver get en línea GNU Assembler para más información sobre este truco-array-miembro flexible para diciendo al compilador que lea toda una serie sin especificar la longitud de forma explícita.

En C se puede definir un nuevo tipo en el interior de un molde, pero no se puede en C ++, de ahí el using en lugar de un operando de entrada muy complicado.

int bar(unsigned howmany)
{
    //int howmany = 5046;
    char buffer[howmany];
    //ext(buffer);
    buffer[0] = 1;
    buffer[100] = 100;   // test whether we got the input constraints right

    //using input_t = const struct {char a[howmany];};  // requires a constant size
    using flexarray_t = const struct {char a; char x[];};
    const char *dummy;
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       : [res]"=a"(result)        // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , "=r" (dummy)           // output operand in the same register as buffer input, so we can modify the register
       : [ptr] "2" (buffer)     // matching constraint for the dummy output
         , "m" (*(flexarray_t *) buffer)  // whole buffer as an input operand

           //, "m" (*buffer)        // just the first element: doesn't stop the buffer[100]=100 store from sinking past the inline asm, even if you used asm volatile
       : // no clobbers
    );
    buffer[100] = 101;
    return result;
}

I también se utiliza una restricción coincidente así buffer podría ser una entrada directamente, y el operando de salida en el mismo registro significa que podemos modificar ese registro. Conseguimos el mismo efecto en foo() utilizando const char *bufptr = buffer; y luego usando una restricción de lectura-escritura para decirle al compilador que el nuevo valor de esa variable C es lo que dejamos en el registro. De cualquier manera que ningún valor en una variable muertos C que se sale del ámbito sin ser leído, pero la forma en la restricción de adaptación puede ser útil para macros en el que no desea modificar el valor de su entrada (y no es necesario el tipo de la entrada:. int dummy funcionaría bien, también)

Las asignaciones buffer[100] = 100; y buffer[100] = 101; están ahí para demostrar que ambos aparecen en la ASM, en vezde ser fusionado a través de la línea-asm (lo que suceda si dejar de lado el operando de entrada "m"). IDK por qué el buffer[100] = 101; no está optimizado de distancia; que está muerto por lo que debe ser. También tenga en cuenta que asm volatile no bloquear este reordenamiento, por lo que no es una alternativa a un clobber "memory" o el uso de las restricciones adecuadas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top