Programação assembler "inline"

Atualizado em 05-Jun-2015
Prof. Célio - MC404C

Aplicações de sistemas embarcados escritas em C muitas vezes precisam, por razões de eficiência, de introduzir código em assembler. Há duas técnicas básicas para este fim:

  1. Escrever uma ou mais subrotinas em assembler e chamá-las a partir do programa em C.

    Esta é a técnica mais simples pois requer apenas a compilação separada pelo gcc do programa em C e das subrotinas e invocar o ligador ld para gerar o programa final executável. A compilação de cada componente do programa deve usar o flag "-c" na linha de comando, que direciona o gcc para gerar um relocável ".o" em vez de um executável. Da mesma forma, se o gcc for invocado com uma lista de relocáveis ".o" ele aciona o ligador ld para "ligar" os relocáveis gerando um executável. Exemplo:
    arm-none-eabi-gcc  -mcpu=cortex-m3 -mthumb -c --specs=rdimon.specs -lc -lrdimon  -g $1 -o $file.o
    compile dessa forma, por exemplo, prog1.c, sub1.s e sub2.s e em seguida execute:
    arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb --specs=rdimon.specs -lc -lrdimon -g prog1.o sub1.o sub2.o -o prog1 
    
    É preciso, no entanto, saber como o programa em C passa parâmetros para uma função. Se "disassemblarmos" um exemplo qualquer (usando, por exemplo, o flag "-S" ao invocar gcc) verificaremos que parâmetros são passados nos registradores r0, r1, etc, e o retorno da função é devolvido em r0 (na entrada da função os parâmetros são colocados na pilha, pois eventualmente outra função pode ser chamada ou até uma chamada recursiva). Como a função chamada, no nosseo caso, está escrita em assembler, fica trivial acessar diretamente os parâmetros em r0, r1, etc e devolver o resultado em r0 (observe que se um parâmetro for o endereço de um array (vetor) é este endereço que é passado no registrador). Exemplo: suponha que uma aplicação em C precise de uma rotina rápida de ordenação que será escrita em assembler. O programa em C poderá usar o seguinte esqueleto:
    #include < stdio.h> 
    int sort(int n, unsigned int array[]); //a rotina sort foi escrita em assembler
    int main(){
      int code;
      // inicialização do array a[] com N elementos
      code= sort(N, a); // chama sort com 2 parâmetros, code é um código de retorno 
      printarray(N, a); // exibe no vídeo o array ordenado
    }
    e para a subrotina sort.s:
    
    .syntax unified
    .align
    .text
    .global sort
    sort:
       push {reg-list,lr} // não empilha r0, que vai ter o código de retorno
       r0 tem o tamanho do array
       r1 tem o endereço do array
       aqui vai inserida  a rotina de ordenação
       suponha que o código de retorno seja 0 para sucesso e 1 para falha
       mov r0, #0
       pop {reg-list, pc}
    
  2. Inserção de instruções em assembler no código em C

    Neste caso será preciso acessar variáveis do programa em C, fazer operações com seus valores e atribuir novos valores para uma ou mais variáveis. Aí surge o problema apelidado de "casamento de impedâncias" pois C e assembler são linguagens muito diferentes. Alem disso pode ser preciso informar ao gcc para não descartar pelo seu otimizador as operações feitas pelo assembler e também se registradores, dados na memória ou até os flags foram alterados pelo programa em assembler. O documento ARM-GCC-Inline-Assembler-Cookbook.pdf dá uma boa introdução ao assunto, será discutido em aula e vários exemplos foram colocados na pasta exemplos/maisexemplos/inline/.
    Abaixo a 1a página do documento:

    Let's start with a simple example of rotating bits. It takes the value of one integer variable, right rotates the bits by one and stores the result in a second integer variable.

    asm("mov %0, %1, ror #1" : "=r" (result) : "r" (value));

    Each asm statement is divided by colons into up to four parts:

    1. The assembler instructions, defined as a single string constant:
    "mov %0, %1, ror #1"

    2. A list of output operands, separated by commas. Our example uses just one:
    "=r" (result) Obs: result é o nome de uma variável do programa em C

    3. A comma separated list of input operands. Again our example uses one operand only:
    "r" (value) Obs: value é o nome de uma variável do programa em C

    4. Clobbered registers, left empty in our example.

    You can write assembler instructions in much the same way as you would write assembler programs. However, registers and constants are used in a different way if they refer to expressions of your C program. The connection between registers and C operands is specified in the second and third part of the asm instruction, the list of output and input operands, respectively. The general form is

    asm(code : output operand list : input operand list : clobber list);