Pregunta

Estoy usando Freescale Kinetis K60 y usando el IDE Codewarrior (que creo que usa GCC para el cumplidor).

Quiero multiplicar dos números de 32 bits (lo que resulta en un número de 64 bits) y solo conserva los 32 bits superiores.

Creo que la instrucción de ensamblaje correcta para el brazo Cortex-M4 es la instrucción SMMUL. Preferiría acceder a esta instrucción desde el código C en lugar del ensamblaje. ¿Cómo hago esto?

Me imagino que el código idealmente sería algo como esto:

int a,b,c;

a = 1073741824;   // 0x40000000 = 0.5 as a D0 fixed point number
b = 1073741824;   // 0x40000000 = 0.5 as a D0 fixed point number

c = ((long long)a*b) >> 31;  // 31 because there are two sign bits after the multiplication
                             // so I can throw away the most significant bit

Cuando intento esto en Codewarrior, obtengo el resultado correcto para C (536870912 = 0.25 como un número D0 FP). No veo la instrucción SMMUL en ningún lado y la multiplicación es de 3 instrucciones (Umull, MLA y MLA, no entiendo por qué está usando una multiplicación sin firmar, pero esa es otra pregunta). También he probado un cambio correcto de 32 ya que eso podría tener más sentido para la instrucción SMMUL, pero eso no hace nada diferente.

¿Fue útil?

Solución

El problema que obtiene con la optimización de ese código es:

08000328 <mul_test01>:
 8000328:   f04f 5000   mov.w   r0, #536870912  ; 0x20000000
 800032c:   4770        bx  lr
 800032e:   bf00        nop

Su código no hace nada de tiempo de ejecución para que el optimizador pueda calcular la respuesta final.

este:

.thumb_func
.globl mul_test02
mul_test02:
    smull r2,r3,r0,r1
    mov r0,r3
    bx lr

llamó con esto:

c = mul_test02(0x40000000,0x40000000);

da 0x10000000

Umull da el mismo resultado porque está usando números positivos, los operandos y los resultados son positivos, por lo que no llega a las diferencias firmadas/sin firmar.

Hmm, bueno, me tienes en este caso. Leí su código como diciéndole al compilador que promocione la multiplica a 64 bits. Smull es dos operandos de 32 bits que dan un resultado de 64 bits, que no es lo que su código está pidiendo ... pero tanto GCC como Clang usaron la sacudida de todos modos, incluso si lo dejé como una función sin tiempo, por lo que no lo sabía en El tiempo de compilación que los operandos no tenían dígitos significativos superiores a 32, todavía usaban Smull.

Quizás el cambio fue la razón.

Sí, eso fue todo ...

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 31; 
    return(c);
}

da

Tanto GCC como Clang (Well Clang recicla R0 y R1 en lugar de usar R2 y R3)

08000340 <mul_test04>:
 8000340:   fb81 2300   smull   r2, r3, r1, r0
 8000344:   0fd0        lsrs    r0, r2, #31
 8000346:   ea40 0043   orr.w   r0, r0, r3, lsl #1
 800034a:   4770        bx  lr

pero esto

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b); 
    return(c);
}

Da esto

GCC:

08000340 <mul_test04>:
 8000340:   fb00 f001   mul.w   r0, r0, r1
 8000344:   4770        bx  lr
 8000346:   bf00        nop

sonido metálico:

0800048c <mul_test04>:
 800048c:   4348        muls    r0, r1
 800048e:   4770        bx  lr

Entonces, con el cambio de bits, los compiladores se dan cuenta de que solo está interesado en la parte superior del resultado para que puedan descartar la parte superior de los operandos, lo que significa que se puede usar Smull.

Ahora si haces esto:

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 32; 
    return(c);
}

Ambos compiladores se vuelven aún más inteligentes, en particular Clang:

0800048c <mul_test04>:
 800048c:   fb81 1000   smull   r1, r0, r1, r0
 8000490:   4770        bx  lr

GCC:

08000340 <mul_test04>:
 8000340:   fb81 0100   smull   r0, r1, r1, r0
 8000344:   4608        mov r0, r1
 8000346:   4770        bx  lr

Puedo ver que 0x40000000 considerado como un flotador donde realiza un seguimiento del lugar decimal, y ese lugar es una ubicación fija. 0x20000000 tendría sentido como la respuesta. Todavía no puedo decidir si ese turno de 31 bits funciona universalmente o simplemente para este caso.

Un ejemplo completo utilizado para lo anterior está aquí

https://github.com/dwelch67/stm32vld/tree/master/stm32f4d/sample01

Y lo ejecuté en un STM32F4 para verificar que funciona y los resultados.

EDITAR:

Si pasa los parámetros a la función en lugar de codificarlos dentro de la función:

int myfun ( int a, int b )
{
     return(a+b);
}

El compilador se ve obligado a hacer código de tiempo de ejecución en lugar de optimizar la respuesta en el momento de la compilación.

Ahora, si llama a esa función desde otra función con números codificados:

...
c=myfun(0x1234,0x5678);
...

En esta función de llamadas, el compilador puede optar por calcular la respuesta y simplemente colocarla allí en el momento de la compilación. Si la función myfun () es global (no se declara como estática), el compilador no sabe si algún otro código que se vincule más tarde lo usará, por lo que incluso cerca del punto de llamada en este archivo optimiza una respuesta, todavía tiene que producir la función real Y déjelo en el objeto para otro código en otros archivos a llamar, para que aún pueda examinar qué hace el compilador/optimizador con ese código C. A menos que use LLVM, por ejemplo, donde puede optimizar todo el proyecto (en todos los archivos), el código externo que llama a esta función usará la función real y no una respuesta calculada por tiempo de compilación.

Tanto GCC como Clang hicieron lo que estoy describiendo, dejó el código de tiempo de ejecución para la función como una función global, pero dentro del archivo calculó la respuesta en el tiempo de compilación y colocó la respuesta codificada en el código en lugar de llamar a la función:

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 31;
    return(c);
}

En otra función en el mismo archivo:

hexstring(mul_test04(0x40000000,0x40000000),1);

La función en sí se implementa en el código:

0800048c <mul_test04>:
 800048c:   fb81 1000   smull   r1, r0, r1, r0
 8000490:   0fc9        lsrs    r1, r1, #31
 8000492:   ea41 0040   orr.w   r0, r1, r0, lsl #1
 8000496:   4770        bx  lr

Pero donde se llama, han codificado la respuesta porque tenían toda la información necesaria para hacerlo:

 8000520:   f04f 5000   mov.w   r0, #536870912  ; 0x20000000
 8000524:   2101        movs    r1, #1
 8000526:   f7ff fe73   bl  8000210 <hexstring>

Si no desea la respuesta codificada, debe usar una función que no esté en el mismo pase de optimización.

La manipulación del compilador y el optimizador se reduce a mucha práctica y no es una ciencia exacta ya que los compiladores y optimizadores evolucionan constantemente (para bien o para bien).
Al aislar un poco de código en una función que está causando problemas de otra manera, es más probable que las funciones más grandes necesiten un marco de pila y las variables desalojadas de los registros a la pila a medida que avanza, las funciones más pequeñas pueden no necesitar hacer eso y las Los optimizadores pueden cambiar la forma en que se implementa el código como resultado. Usted prueba el fragmento de código una forma de ver qué está haciendo el compilador y luego lo usa en una función más grande y no obtenga el resultado que desea. Si hay una instrucción exacta o secuencia de instrucciones que desea implementar ... Impléngalas en el ensamblador. Si estaba apuntando a un conjunto específico de instrucciones en un conjunto/procesador de instrucciones específico, evite el juego, evite que su código cambie cuando cambie las computadoras/compiladores/etc., y simplemente use el ensamblador para ese objetivo. Si es necesario, o use opciones de compilación condicional para construir diferentes objetivos sin el ensamblador.

Otros consejos

GCC admite tipos reales de puntos fijos: http://gcc.gnu.org/onlinedocs/gcc/fixed_002dpoint.html

No estoy seguro de qué instrucción usará, pero podría hacerte la vida más fácil.

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