¿Cómo afectar la generación de código Delphi XEx para objetivos Android / ARM?

266

Actualización 2017-05-17. Ya no trabajo para la empresa donde se originó esta pregunta y no tengo acceso a Delphi XEx. Mientras estuve allí, el problema se resolvió migrando a FPC + GCC mixto (Pascal + C), con NEON intrínsecos para algunas rutinas en las que marcó la diferencia. (FPC + GCC es muy recomendable también porque permite el uso de herramientas estándar, particularmente Valgrind.) Si alguien puede demostrar, con ejemplos creíbles, cómo puede realmente producir código ARM optimizado desde Delphi XEx, me complace aceptar la respuesta .


Los compiladores Delphi de Embarcadero utilizan un back-end LLVM para producir código ARM nativo para dispositivos Android. Tengo grandes cantidades de código Pascal que necesito compilar en aplicaciones de Android y me gustaría saber cómo hacer que Delphi genere un código más eficiente. En este momento, ni siquiera estoy hablando de funciones avanzadas como las optimizaciones automáticas SIMD, solo de producir un código razonable. ¿Seguramente debe haber una manera de pasar parámetros al lado LLVM, o de alguna manera afectar el resultado? Por lo general, cualquier compilador tendrá muchas opciones para afectar la compilación y la optimización del código, pero los objetivos ARM de Delphi parecen ser solo "activación / desactivación de optimización" y eso es todo.

Se supone que LLVM es capaz de producir código razonablemente ajustado y sensible, pero parece que Delphi está usando sus instalaciones de una manera extraña. Delphi quiere usar mucho la pila, y generalmente solo utiliza los registros del procesador r0-r3 como variables temporales. Quizás el más loco de todos, parece estar cargando enteros normales de 32 bits como cuatro operaciones de carga de 1 byte. ¿Cómo hacer que Delphi produzca un mejor código ARM, y sin la molestia byte por byte que está haciendo para Android?

Al principio pensé que la carga byte por byte era para intercambiar el orden de bytes de big-endian, pero ese no fue el caso, en realidad solo se trata de cargar un número de 32 bits con 4 cargas de un solo byte. * Podría ser cargar los 32 bits completos sin realizar una carga de memoria de tamaño de palabra no alineada. (si DEBERÍA evitar eso es otra cosa, lo que indicaría que todo es un error del compilador) *

Veamos esta simple función:

function ReadInteger(APInteger : PInteger) : Integer;
begin
  Result := APInteger^;
end;

Incluso con las optimizaciones activadas, Delphi XE7 con el paquete de actualización 1, así como XE6, producen el siguiente código de ensamblaje ARM para esa función:

Disassembly of section .text._ZN16Uarmcodetestform11ReadIntegerEPi:

00000000 <_ZN16Uarmcodetestform11ReadIntegerEPi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   78c1        ldrb    r1, [r0, #3]
   a:   7882        ldrb    r2, [r0, #2]
   c:   ea42 2101   orr.w   r1, r2, r1, lsl #8
  10:   7842        ldrb    r2, [r0, #1]
  12:   7803        ldrb    r3, [r0, #0]
  14:   ea43 2202   orr.w   r2, r3, r2, lsl #8
  18:   ea42 4101   orr.w   r1, r2, r1, lsl #16
  1c:   9101        str r1, [sp, #4]
  1e:   9000        str r0, [sp, #0]
  20:   4608        mov r0, r1
  22:   b003        add sp, #12
  24:   bd80        pop {r7, pc}

Solo cuente la cantidad de instrucciones y accesos de memoria que Delphi necesita para eso. Y construir un número entero de 32 bits a partir de 4 cargas de un solo byte ... Si cambio un poco la función y uso un parámetro var en lugar de un puntero, es un poco menos complicado:

Disassembly of section .text._ZN16Uarmcodetestform14ReadIntegerVarERi:

00000000 <_ZN16Uarmcodetestform14ReadIntegerVarERi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   6801        ldr r1, [r0, #0]
   a:   9101        str r1, [sp, #4]
   c:   9000        str r0, [sp, #0]
   e:   4608        mov r0, r1
  10:   b003        add sp, #12
  12:   bd80        pop {r7, pc}

No incluiré el desmontaje aquí, pero para iOS, Delphi produce un código idéntico para las versiones de puntero y parámetro var, y son casi, pero no exactamente, las mismas que la versión de parámetro var de Android. Editar: para aclarar, la carga byte por byte es solo en Android. Y solo en Android, las versiones de puntero y parámetro var difieren entre sí. En iOS, ambas versiones generan exactamente el mismo código.

A modo de comparación, esto es lo que piensa FPC 2.7.1 (versión troncal SVN de marzo de 2014) de la función con nivel de optimización -O2. Las versiones de los parámetros puntero y var son exactamente iguales.

Disassembly of section .text.n_p$armcodetest_$$_readinteger$pinteger$$longint:

00000000 <P$ARMCODETEST_$$_READINTEGER$PINTEGER$$LONGINT>:

   0:   6800        ldr r0, [r0, #0]
   2:   46f7        mov pc, lr

También probé una función C equivalente con el compilador C que viene con el NDK de Android.

int ReadInteger(int *APInteger)
{
    return *APInteger;
}

Y esto se compila esencialmente en lo mismo que FPC hizo:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Lado S. Fresco
fuente
14
Por cierto en la discusión de Google+ sobre esto, Sam Shaw señala que C ++ muestra el código de formato largo en las compilaciones de depuración y el código optimizado en el lanzamiento. Wheres Delphi lo hace en ambos. Entonces, a partir de eso, podría ser un simple error en las banderas que están enviando LLVM, y si es así, vale la pena presentar un informe de error, podría solucionarse muy pronto.
David
9
Oh, ok, leí mal. Entonces, como dijo Notlikethat, parece que asume que la carga del puntero estaría desalineada (o no puede garantizar la alineación), y las plataformas ARM más antiguas no necesariamente pueden hacer cargas desalineadas. Asegúrese de tener una orientación de compilación en armeabi-v7alugar de armeabi(no estoy seguro de si existen tales opciones en este compilador), ya que las cargas no alineadas deben ser compatibles desde ARMv6 (mientras que armeabisupone ARMv5). (El desmontaje se muestra no se ve como se lee un valor bigEndian, sólo se lee un poco valor endian un byte a la vez.)
mstorsjo
66
Encontré RSP-9922 que parece ser este mismo error.
David
66
Alguien había preguntado acerca de la ruptura de la optimización entre XE4 y XE5, en el grupo de noticias embarcadero.public.delphi.platformspecific.ios, "¿Se ha roto la optimización del compilador ARM?" devsuperpage.com/search/…
Side S. Fresh
66
@Johan: ¿qué ejecutable es? Tenía la impresión de que de alguna manera estaba cocido dentro del ejecutable del compilador de Delphi. Pruébalo y dinos los resultados.
Side S. Fresh

Respuestas:

8

Estamos investigando el problema. En resumen, depende de la posible desalineación potencial (hasta el límite 32) del Entero al que hace referencia un puntero. Necesito un poco más de tiempo para tener todas las respuestas ... y un plan para abordar esto.

Marco Cantù, moderador en Delphi Developers

Consulte también ¿Por qué las bibliotecas zlib y zip de Delphi son tan lentas por debajo de 64 bits? ya que las bibliotecas Win64 se envían construidas sin optimizaciones.


En el Informe de QP: Código ARM incorrecto RSP-9922 producido por el compilador, ¿se ignora la directiva $ O? Marco agregó la siguiente explicación:

Hay varios problemas aquí:

  • Como se indicó, la configuración de optimización se aplica solo a los archivos de la unidad completa y no a las funciones individuales. En pocas palabras, activar y desactivar la optimización en el mismo archivo no tendrá ningún efecto.
  • Además, simplemente tener habilitada la "Información de depuración" desactiva la optimización. Por lo tanto, cuando uno está depurando, activar explícitamente las optimizaciones no tendrá ningún efecto. En consecuencia, la vista de CPU en el IDE no podrá mostrar una vista desmontada de código optimizado.
  • Tercero, cargar datos de 64 bits no alineados no es seguro y genera errores, de ahí las operaciones separadas de 4 bytes que se necesitan en escenarios dados.
Kirk Strobeck
fuente
Marco Cantù publicó esa nota "Estamos investigando el problema" en enero de 2015, y el informe de error relacionado RSP-9922 se marcó resuelto con la resolución "Funciona como se esperaba" en enero de 2016, y hay una mención "problema interno cerrado el 2 de marzo, 2015 ". No entiendo sus explicaciones.
Side S. Fresh
1
Agregué un comentario en la resolución del problema.
Marco Cantù