Convenção de chamada ARM para C, registra para salvar

Já faz um tempo desde a última vez que codifiquei o braço de montagem e estou um pouco enferrujado nos detalhes. Se eu chamar uma função C do braço, eu só tenho que me preocupar em salvar r0-r3 e lr, certo? Se a função C usa algum outro registrador, é responsável por salvar os que estão na pilha e restaurá-los? Em outras palavras, o compilador geraria código para fazer isso para funções C. Por exemplo, se eu usar o r10 em uma função assembler, eu não tenho que empurrar seu valor na pilha, ou para a memória, e pop/restore após uma chamada C, eu faço?

Isto é para o arm-eabi-gcc 4.3.0.

Eu percebo que eu poderia ler todo o EABI, mas o RTFM é o que o SO é para, certo? :-)

0
Aqui está um link externo que pode ser útil. Introdução do APCS , especialmente alguns nomes diferentes para uso do register .
adicionado o autor artless noise, fonte

5 Respostas

To add up missing info on NEON registers:

De o AAPCS , §5.1.1 Registros principais:

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
  • r4-r8 are callee-save registers
  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
  • r10-r11 are callee-save registers
  • r12-r15 are special registers

A partir do AAPCS, §5.1.2.1 convenções de uso de registros VFP:

  • s16–s31 (d8–d15, q4–q7) must be preserved
  • s0–s15 (d0–d7, q0–q3) and d16–d31 (q8–q15) do not need to be preserved

Original post:
arm-to-c-calling-convention-neon-registers-to-save

0
adicionado

For 64-bit ARM, A64 (from Procedure Call Standard for the ARM 64-bit Architecture)

Existem trinta e um registradores de uso geral (inteiros) de 64 bits visíveis para o conjunto de instruções A64; estes são rotulados r0-r30 . Em um contexto de 64 bits, esses registros são normalmente referenciados usando os nomes x0-x30 ; em um contexto de 32 bits, os registros são especificados usando w0-w30 . Além disso, um registrador de ponteiro de pilha, SP , pode ser usado com um número restrito de instruções.

  • SP The Stack Pointer
  • r30 LR The Link Register
  • r29 FP The Frame Pointer
  • r19…r28 Callee-saved registers
  • r18 The Platform Register, if needed; otherwise a temporary register.
  • r17 IP1 The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
  • r16 IP0 The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
  • r9…r15 Temporary registers
  • r8 Indirect result location register
  • r0…r7 Parameter/result registers

Os primeiros oito registradores, r0-r7 , são usados ​​para passar valores de argumento em uma sub-rotina e retornar valores de resultado de uma função. Eles também podem ser usados ​​para manter valores intermediários dentro de uma rotina (mas, em geral, somente entre chamadas de sub-rotina).

Registros r16 (IP0) e r17 (IP1) podem ser usados ​​por um linker como um registrador de rascunho entre uma rotina e qualquer subrotina chamada. Eles também podem ser usados ​​em uma rotina para manter valores intermediários entre as chamadas de sub-rotina.

O papel do registro r18 é específico da plataforma. Se uma ABI de plataforma tiver necessidade de um registrador de propósito geral dedicado para transportar o estado interprocessual (por exemplo, o contexto de encadeamento), ele deverá usar esse registro para essa finalidade. Se a ABI da plataforma não tiver tais requisitos, deverá usar o r18 como um registro temporário adicional. A especificação ABI da plataforma deve documentar o uso desse registro.

SIMD

A arquitetura ARM de 64 bits também possui mais trinta e dois registradores, v0-v31 , que podem ser usados ​​pelas operações SIMD e Floating-Point. O nome preciso do registro mudará indicando o tamanho do acesso.

Note: Unlike in AArch32, in AArch64 the 128-bit and 64-bit views of a SIMD and Floating-Point register do not overlap multiple registers in a narrower view, so q1, d1 and s1 all refer to the same entry in the register bank.

Os primeiros oito registradores, v0-v7 , são usados ​​para passar valores de argumentos para uma sub-rotina e para retornar valores de resultados de uma função. Eles também podem ser usados ​​para manter valores intermediários dentro de uma rotina (mas, em geral, somente entre chamadas de sub-rotina).

Os registros v8-v15 devem ser preservados por um chamado nas chamadas de sub-rotina; os registros restantes ( v0-v7, v16-v31 ) não precisam ser preservados (ou devem ser preservados pelo chamador). Além disso, apenas os 64 bits de baixo de cada valor armazenado em v8-v15 precisam ser preservados; é responsabilidade do chamador preservar valores maiores.

0
adicionado

Depende da ABI da plataforma para a qual você está compilando. No Linux, existem duas ABIs ARM; o antigo e o novo. AFAIK, o novo (EABI) é de fato o AAPCS da ARM. As definições completas do EABI atualmente vivem aqui no ARM infocenter .

De AAPCS, §5.1.1 :

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
  • r4-r8 are callee-save registers
  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
  • r10-r11 are callee-save registers
  • r12-r15 are special registers

Um registrador de salvamento de chamada deve ser salvo pelo receptor (em oposição a um registro de salvar o chamador, em que o chamador salva o registrador); então, se esta é a ABI que você está usando, você não precisa salvar r10 antes de chamar outra função (a outra função é responsável por salvá-la).

Edit: Which compiler you are using makes no difference; gcc in particular can be configured for several different ABIs, and it can even be changed on the command line. Looking at the prologue/epilogue code it generates is not that useful, since it is tailored for each function and the compiler can use other ways of saving a register (for instance, saving it in the middle of a function).

0
adicionado
"Você pode baixar toda a especificação ABI e seus documentos de suporte e código de exemplo como um arquivo ZIP desta página." Arquivo Zip: infocenter.arm.com/help/topic /com.arm.doc.ihi0036b/bsabi.zip
adicionado o autor jww, fonte
Obrigado, isso parece tocar alguns sinos. Eu acho que o primeiro "r0-r4" na sua lista é um erro de digitação, certo? +1 (e provavelmente a melhor resposta a menos que haja uma mudança radical)
adicionado o autor richq, fonte
Eu estou apenas passando por este documento PCS e tenho essa dúvida, os registros de variáveis ​​v1-v8 são usados ​​para salvar variáveis ​​locais, em caso afirmativo, o que acontece quando aloco mais variáveis ​​locais? Não consigo conectar pilha e esses registros ...
adicionado o autor Xavier Geoffrey, fonte
Sim, foi um erro de digitação (e não o único, mas consertei os outros antes de passar pela primeira vez - ou assim espero).
adicionado o autor CesarB, fonte
Para resumir: Ao chamar uma função C, os registros r0-r3, r12 (e talvez r9) precisam ser salvos. Pela minha experiência, o gcc usa o r12 como um registrador de rascunho dentro de uma função e, portanto, não é salvo pelo receptor mesmo se o interpolação de braço/polegar não for usado. Em caso de interfuncionamento, o vinculador irá gerar código de cola que usa r12 se uma função de braço chamar uma função de polegar.
adicionado o autor Sven, fonte
O comentário de Alex é confuso, pois é do ponto de vista do candidato. A questão discutida aqui é do ponto de vista do chamador. Um chamador NÃO precisa salvar r4-r11 ao chamar uma função C. A função C (o chamado) salvará esses registros. Além disso, por que ninguém esclarece se o r9 precisa ser salvo pelo chamador ou não? Eu acredito que para uma toolchain arm-eabi-gcc, o r9 também é salvo pelo receptor. Quem pode apontar uma fonte de informação que resolve a questão da r9?
adicionado o autor Sven, fonte
Eu acho que é muito mais fácil lembrar que você tem que salvar e restaurar r4-r11 caso você queira usá-los; é por isso que eles são salvos.
adicionado o autor amc, fonte
Para estender o comentário do amorenoc: r4-r11 (talvez com a exceção de r9 ) pode ser considerado "seguro" ao chamar uma função. r0-r3 provavelmente não será preservado após a chamada da função, e dependendo de como a ligação é feita, nem o r12 (que pode ser usado como um registrador de rascunho).
adicionado o autor Leo, fonte

As respostas de CesarB e Pavel forneceram citações da AAPCS, mas questões pendentes permanecem. O callee salva r9? E quanto a r12? E quanto a r14? Além disso, as respostas foram muito gerais, e não específicas para o conjunto de ferramentas arm-eabi, conforme solicitado. Aqui está uma abordagem prática para descobrir quais registros são salvos pelo callee e quais não são.

O seguinte código C contém um bloco de montagem embutido, que reivindica modificar os registradores r0-r12 e r14. O compilador irá gerar o código para salvar os registros requeridos pela ABI.

void foo() {
  asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14");
}

Use a linha de comando arm-eabi-gcc-4.7-O2 -S-o - foo.c e adicione os switches para sua plataforma (como -mcpu = arm7tdmi por exemplo). O comando imprimirá o código de montagem gerado no STDOUT. Pode parecer algo assim:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    bx  lr

Note que o código gerado pelo compilador salva e restaura o r4-r11. O compilador não salva r0-r3, r12. Que restaura r14 (alias lr) é puramente acidental, pois sei por experiência que o código de saída também pode carregar o lr salvo em r0 e então fazer um "bx r0" em vez de "bx lr". Ou adicionando o -mcpu = arm7tdmi -mno-thumb-interwork ou usando -mcpu = cortex-m4 -mthumb obtemos um código de assembly ligeiramente diferente que se parece com isto:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

Novamente, r4-r11 são salvos e restaurados. Mas r14 (alias lr) não é restaurado.

Para resumir:

  • r0-r3 são não callee-saved
  • r4-r11 são salvos pelo calendário
  • r12 (alias ip) não é salvo pelo callee
  • r13 (alias sp) é salvo pelo callee
  • r14 (alias lr) não é salvo pelo callee
  • r15 (alias pc) é o contador do programa e é configurado para o valor de lr antes da chamada de função

Isso vale pelo menos para os padrões do arm-eabi-gcc. Existem opções de linha de comando (em particular a opção -mabi) que podem influenciar os resultados.

0
adicionado
Ah, os registradores são bancados por interrupções exatamente por esse motivo. De qualquer forma, concordamos em discordar.
adicionado o autor artless noise, fonte
Eu vejo que você tem um ponto em um sentido hipotético; Você poderia escrever algum montador que retornasse um ponteiro de função em lr . No entanto, não vejo o que ter o valor original de lr faria por você. Você está executando o código que estava no lr ao retornar, então seu valor original é explícito pelo código em execução. Bem resumido por Pavel como r12-r15 são registros especiais . O valor do lr na chamada será o valor do pc na saída. A questão de se o lr foi restaurado ou não parece estranh
adicionado o autor artless noise, fonte
Veja link ARM e ponteiro de quadro para detalhes sobre pc e lr . r12 também é conhecido como ip e pode ser usado durante um prólogo e epílogo . É um registro volátil . Isso é importante para rotinas que estão analisando a pilha/quadros de chamadas.
adicionado o autor artless noise, fonte
Sua análise está incorreta </​​i>; o lr é exibido como o pc para uma maneira mais rápida de retornar. A resposta à sua pergunta r9 está no APCS . É chamado base estática neste documento e a seção Reentrant vs Non-Reentrant Code é relativa. O APCS suporta várias configurações, mas gcc é geralmente re-entrante sem limites de pilha . Especialmente,
adicionado o autor artless noise, fonte
Isso é exatamente o que eu estava procurando - uma maneira de descobrir quais registros são preservados pelas configurações específicas do compilador que estou usando para o meu projeto. Obrigado!
adicionado o autor TonyK, fonte
Em que sentido minha análise sobre lr está incorreta? Eu acho que você me interpretou mal. De qualquer forma, eu estava apresentando o segundo snippet de código de assembly, pois o primeiro parecia que o lr era salvo pelo callee. No entanto, acho que não é. Sim, no segundo trecho, lr é exibido como pc como uma maneira mais rápida de retornar e eu não expliquei isso, mas o ponto de apresentar o segundo trecho foi que mostra que lr não é salvo por salva.
adicionado o autor Sven, fonte
É verdade que lr é restaurado para o pc . Mas não é verdade, que se pode esperar que o valor de lr seja restaurado. Eu não vejo como isso pode estar errado. Que o valor acaba em um registrador que não é lr é completamente irrelevante para a questão de se lr é restaurado ou não. Você está certo que o conjunto de registros que é restaurado e não restaurado pode mudar conforme a opção -mabi é alterada.
adicionado o autor Sven, fonte
No momento, estou escrevendo um wrapper assembler para interrupções aninhadas no ARM7TDMI. Se o valor de lr é preservado por uma chamada para o código C é importante, como o valor de lr deve ser restaurado para seu valor anterior pelo warpper do assembler. Portanto, saber se o lr é salvo pelo callee não é tão hipotético.
adicionado o autor Sven, fonte

Há também uma diferença na arquitetura do Cortex M3 para chamadas e interrupções de função.

Se ocorrer uma interrupção, será feito o envio automático de R0-R3, R12, LR, PC para a pilha e, quando for devolvido, o POP automático de IRQ. Se você usar outros registradores na rotina de IRQ, você deve empurrá-los/empurrá-los para o Stack manualmente.

Eu não acho que este PUSH automático e POP é feito para uma chamada de função (instrução de salto). Se a convenção diz que o R0-R3 pode ser usado apenas como um argumento, resultado ou registros de rascunho, então não há necessidade de armazená-los antes da chamada de função, porque não deve haver nenhum valor usado depois do retorno da função. Mas, da mesma forma que em uma interrupção, você precisa armazenar todos os outros registradores da CPU, se você usá-los em sua função.

0
adicionado