Como afetar a geração de código Delphi XEx para destinos Android / ARM?

266

Atualização 2017-05-17. Não trabalho mais na empresa em que essa pergunta se originou e não tenho acesso ao Delphi XEx. Enquanto eu estava lá, o problema foi resolvido migrando para FPC + GCC misto (Pascal + C), com NEON intrínseco para algumas rotinas em que fazia diferença. (FPC + GCC é altamente recomendado também porque permite o uso de ferramentas padrão, particularmente Valgrind.) Se alguém puder demonstrar, com exemplos confiáveis, como é capaz de produzir código ARM otimizado a partir do Delphi XEx, fico feliz em aceitar a resposta .


Os compiladores Delphi da Embarcadero usam um back-end LLVM para produzir código ARM nativo para dispositivos Android. Tenho grandes quantidades de código Pascal que preciso compilar em aplicativos Android e gostaria de saber como fazer o Delphi gerar código mais eficiente. No momento, nem estou falando de recursos avançados, como otimizações automáticas do SIMD, apenas sobre a produção de código razoável. Certamente deve haver uma maneira de passar parâmetros para o lado LLVM, ou de alguma forma afetar o resultado? Normalmente, qualquer compilador terá muitas opções para afetar a compilação e otimização de código, mas os alvos ARM do Delphi parecem ser apenas "otimização on / off" e é isso.

Supõe-se que o LLVM seja capaz de produzir código razoavelmente rígido e sensível, mas parece que o Delphi está usando suas instalações de uma maneira estranha. O Delphi quer usar muito a pilha, e geralmente utiliza apenas os registros do processador r0-r3 como variáveis ​​temporárias. Talvez o mais louco de todos, parece estar carregando inteiros normais de 32 bits como quatro operações de carregamento de 1 byte. Como fazer com que o Delphi produza melhor código ARM, e sem o incômodo de byte a byte que está causando para o Android?

No começo, pensei que o carregamento de byte a byte era para trocar a ordem de bytes do big-endian, mas não era esse o caso, era realmente apenas carregar um número de 32 bits com 4 cargas de um byte. * Pode ser o carregamento os 32 bits completos sem fazer uma carga de memória do tamanho de palavra desalinhada. (se DEVE evitar isso é outra coisa, o que sugere que a coisa toda é um bug do compilador) *

Vamos olhar para esta função simples:

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

Mesmo com as otimizações ativadas, o Delphi XE7 com atualização 1, assim como o XE6, produz o seguinte código de montagem do ARM para essa função:

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}

Basta contar o número de instruções e os acessos à memória que o Delphi precisa para isso. E construindo um número inteiro de 32 bits a partir de 4 cargas de byte único ... Se eu mudar a função um pouco e usar um parâmetro var em vez de um ponteiro, ele será um pouco 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}

Não incluirei a desmontagem aqui, mas, para iOS, o Delphi produz código idêntico para as versões dos parâmetros apontador e var, e elas são quase mas não exatamente iguais à versão do parâmetro var Android. Editar: para esclarecer, o carregamento de byte a byte é apenas no Android. E apenas no Android, as versões dos parâmetros apontador e var diferem entre si. No iOS, ambas as versões geram exatamente o mesmo código.

Para comparação, veja o que o FPC 2.7.1 (versão de tronco SVN de março de 2014) pensa da função com nível de otimização -O2. As versões dos parâmetros apontador e var são exatamente iguais.

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

Também testei uma função C equivalente com o compilador C que acompanha o Android NDK.

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

E isso compila basicamente a mesma coisa que o FPC criou:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Side S. Fresh
fonte
14
Entre a discussão do Google+ sobre isso, Sam Shaw observa que o C ++ mostra o código de formato longo nas compilações de depuração e o código otimizado na versão. Onde Delphi faz isso em ambos. Portanto, pode ser um bug simples nos sinalizadores que estão enviando LLVM; se um relatório de bug vale a pena ser arquivado, pode ser corrigido em breve.
David
9
Ah, ok, eu interpretei errado. Então, como Notlikethat disse, parece que assume que a carga do ponteiro estaria desalinhada (ou não pode garantir o alinhamento), e as plataformas ARM mais antigas não podem necessariamente fazer cargas desalinhadas. Certifique-se de que ele construa a segmentação em armeabi-v7avez de armeabi(não tenho certeza se existem opções nesse compilador), pois cargas desalinhadas devem ser suportadas desde o ARMv6 (enquanto armeabiassume o ARMv5). (A mostrado desmontagem não se parece com ele lê um valor bigendian, ele só lê um valor um pouco endian um byte de cada vez.)
mstorsjo
6
Eu encontrei o RSP-9922, que parece ser o mesmo bug.
David
6
Alguém perguntou sobre a otimização sendo interrompida entre o XE4 e o XE5, no grupo de notícias embarcadero.public.delphi.platformspecific.ios, "Otimização do ARM Compiler quebrada?" devsuperpage.com/search/…
Side S. Fresh
6
@Johan: que executável é? Eu tive a impressão de que de alguma forma estava dentro do executável do compilador Delphi. Experimente e informe-nos os resultados.
Side S. Fresh

Respostas:

8

Estamos investigando o problema. Em resumo, depende do potencial desalinhamento (até o limite 32) do Inteiro referenciado por um ponteiro. Precisa de um pouco mais de tempo para ter todas as respostas ... e um plano para resolver isso.

Marco Cantù, moderador em Delphi Developers

Referência também Por que as bibliotecas Delphi zlib e zip são tão lentas com menos de 64 bits? como as bibliotecas Win64 são enviadas construídas sem otimizações.


No relatório QP: Código ARP incorreto RSP-9922 produzido pelo compilador, a diretiva $ O ignorada? , Marco acrescentou a seguinte explicação:

Existem vários problemas aqui:

  • Conforme indicado, as configurações de otimização aplicam-se apenas a arquivos inteiros da unidade e não a funções individuais. Simplificando, ativar e desativar a otimização no mesmo arquivo não terá efeito.
  • Além disso, simplesmente ter "Informações de depuração" ativadas desativa a otimização. Portanto, quando alguém está depurando, ativar explicitamente as otimizações não terá efeito. Consequentemente, a visualização da CPU no IDE não poderá exibir uma visualização desmontada do código otimizado.
  • Terceiro, o carregamento de dados de 64 bits não alinhados não é seguro e resulta em erros, portanto, as operações separadas de 4 bytes que são necessárias em determinados cenários.
Kirk Strobeck
fonte
Marco Cantù postou essa nota "Estamos investigando o problema" em janeiro de 2015, e o relatório de bug relacionado RSP-9922 foi marcado como resolvido com a resolução "Funciona como esperado" em janeiro de 2016, e há uma menção "problema interno encerrado em 2 de março de 2015 ". Eu não entendo suas explicações.
Side S. Fresh
1
Eu adicionei um comentário na resolução do problema.
precisa