Há cerca de um mês atrás o Sérgio Prado lançou um desafio no seu blog: escrever um código em assembly do RX para ordenar um array de inteiros. Achei interessante a idéia e aproveitei a oportunidade para praticar um pouquinho mais a programação destes poderosos microcontroladores de 32 bits da Renesas.

No entanto, logo no início me deparei com um problema: como utilizar assembly dentro do compilador CC-RX?

Embora o próprio Sérgio Prado tenha fornecido os links para o manual de software da família RX e para o manual do compilador CC-RX, o fato é que nenhum deles trata muito claramente do tema. O manual do software RX cobre a conjunto de instruções assembly e a arquitetura da CPU RX, mas não trata de como escrever um programa em assembly. O manual do compilador CC-RX, por outro lado, aborda apenas sucintamente o tema, mas as informações são esparsas, tornando trabalhoso o entendimento de quem deseja conhecer mais sobre o tema. É por isso que tomei a iniciativa de escrever este artigo, numa tentativa de consolidar algumas informações acerca deste tema.

Existem basicamente duas formas de se mesclar C e Assembly numa mesma aplicação: embutindo Assembly dentro do código C (Inline Assembly) ou criando um arquivo Assembly externo, cabendo ao linker o trabalho de mesclar os dois códigos.

Antes de apresentarmos estas duas modalidades de utilização de C e Assembly, é importante verificar como o CC-RX estrutura os parâmetros de chamada de uma função e como o resultado de uma função é retornado para o chamador.

Interface de Chamada de Função

Em virtude da grande disponibilidade de registradores nos microcontroladores RX, o CC-RX utiliza uma interface de chamada de função baseada fortemente nos registradores da CPU. Usualmente o compilador utiliza os registradores R1, R2, R3 e R4 para a passagem de parâmetros da função. No caso de funções com mais de quatro parâmetros, os parâmetros excedentes são passados através da pilha.

Desta forma, uma função com dois parâmetros formais utilizará os registradores R1 e R2 para a passagem dos mesmos (R1 irá conter o primeiro parâmetros e R2 o segundo).

No retorno de uma função, o resultado será armazenado em R1 (tipos de até 32 bits), ou utilizando os registradores R1 a R4 no caso de dados ou estruturas maiores (estruturas grandes são passadas por referência).

Um detalhe importante: lembre-se de que o registrador R0 é utilizado para armazenar o apontador de pilha atual (ISP ou USP)!

Assembly Inline

Assembly inline consiste em utilizar instruções Assembly dentro de um programa escrito em C, ou seja, permite misturar trechos de alto nível (C) com trechos de baixo nível (Assembly). Este tipo de solução é utilizada para se escrever pequenos trechos de código otimizado mas, como veremos adiante, não é adequado para escrever código Assembly mais complexo.

No CC-RX, o Assembly inline está disponível através da diretiva #pragma inline_asm que informa ao compilador que a função especificada será escrita em Assembly.

Repare que as instruções Assembly são escritas diretamente dentro do corpo da função C e não é necessário incluir uma instrução de retorno (RTS ou RTSD) pois o próprio compilador se encarrega disso.

O Assembly inline pode ser muito útil para a escrita de funções otimizadas, mas possui algumas limitações: não é possível utilizar diretivas do assembler e somente rótulos temporários são permitidos.

Rótulos temporários são aqueles definidos através de um ponto de interrogação seguido por dois pontos “?:”. Para saltar para um rótulo temporário localizado a frente, utiliza-se o símbolo ?+ e para saltar para um rótulo atrás utiliza-se ?-. Somente é possível saltar para um rótulo a frente ou um atrás.

A seguir temos um pequeno exemplo de uma função Assembly que calcula o fatorial de um número:

Fica claro que o Assembly inline não é adequado para a escrita da nossa função de ordenação, já que ela vai precisar de desvios de vários níveis, o que não é suportado pelo CC-RX. A solução então é utilizar um arquivo Assembly externo.

Assembly Externo

Outra forma de se mesclar C e Assembly é através da utilização de um arquivo separado contendo o código Assembly da função.

Para a interface do programa em C com uma função externa em Assembly, é necessário definir a mesma como externa no código C. Supondo a função asm_sort, responsável por ordenar uma array de N vetores em ordem ascendente, devemos declarar um protótipo externo para a mesma no início do código C:

No arquivo contendo o código-fonte Assembly, deveremos declarar um símbolo global com o nome da função precedido por um underscore. Isto é feito através da diretiva .GLB do assembler. No caso da função asm_sort em C, o símbolo da mesma deverá ser _asm_sort.

O código-fonte em Assembly também precisa especificar (através da diretiva .SECTION) a sessão de memória onde o mesmo será armazenado. Usualmente o código deve ser linkado na sessão P com atributo CODE.

A seguir temos  listagem do código que escrevi para participar do contest proposto pelo Sérgio Prado. Esta listagem foi adaptada para a interface padrão de chamada de função do CC-RX, já que no contest o endereço da array deveria ser passado em R5 e o tamanho da mesma em R6. O código também preserva o conteúdo dos registradores R10 e R11 que são utilizados pela função Assembly (os registradores R1 a R5 não necessitam ser preservados já que o compilador assume que os mesmos são alterados pela função).

O código que enviei para o Sérgio Prado resultou em um tamanho total de 36 bytes, mas foi superado pela versão enviada por George Tavares que implementou uma versão do Gnome sort (que eu não conhecia) e resultou num tamanho total de código-objeto de 32 bytes! Os resultados estão aqui.

Links Relacionados

Mesclando C e Assembly no compilador CC-RX
Classificado como:                    

Deixe uma resposta