Modulo VI Escrevendo código em Linguagem de Montagem (Assembly) Conjunto de Instruções Arquitetura RISC Reduced Instruction Set Computer Operação com dados se dão nos registradores, nunca na memória Instruções de carga (Load) de dados da memória em registradores Instruções de armazenagem (Store) de dados de registradores para a memória Formato Típico das Instruções Assembly MOV{<cond>}{S} <Rd>, <operador_de_deslocamento> Itens entre { } são opcionais Itens entre < > são uma breve descrição sobre um tipo de valor que será passado pelo programador Muitas instruções podem ser executadas condicionalmente Isso pode evitar os desvios (branch). Desvios podem prejudicar o desempenho de arquiteturas com muitos níveis no pipeline Formato Típico das Instruções Assembly MOV{<cond>}{S} <Rd>, <operador_de_deslocamento> Muitas instruções podem alterar ou não os bits de estados dependendo da forma da instrução Facilita a comparação de dados (pos, neg, zero..) Operações aritméticas em dados não relacionados podem ocorrer durante uma pendência, sem alterar os bits de estados O último operando é um operador de deslocamento, outros são registradores. Formato Típico das Instruções Assembly Em C if (a > b) c = a; else c = b; Considere que R0, R1 e R2 estão carregados com os valores de a, b e c, respectivamente Em Assembly cmp ble mov b R0, R1 L1 R2, R0 L2 mov R2, R1 L1: L2: ou cmp R0, R1 movgt R2, R0 movle R2, R1 Formato do operador_de_deslocamento MOV{<cond>}{S} <Rd>, <operador_de_deslocamento> #<immediate> - Valor pode receber somente certos valores <Rm> - Conteúdo de Rm <Rm>, LSL #<shift_imm> - Conteúdo de Rm deslocado logicamente à esquerda por um valor imediato, exemplo: R2, LSL #5 <Rm>,LSL <Rs> - Conteúdo de Rm é deslocado logicamente à esquerda por um valor contido em Rs Formato do operador_de_deslocamento MOV{<cond>}{S} <Rd>, <operador_de_deslocamento> <Rm>, LSR #<shift_imm> - Conteúdo de Rm é deslocado logicamente à direita por um valor imediato <Rm>, LSR <Rs> - Conteúdo de Rm é deslocado logicamente à direita por um valor contido em Rs <Rm>, ASR #<shift_imm> - Conteúdo de Rm é deslocado para direita aritmeticamente por um valor imediato, com ou sem sinal <Rm>, ASR <Rs> - Conteúdo de Rm é deslocado aritmeticamente à direita por um valor contido em Rs, com ou sem sinal Formato do operador_de_deslocamento MOV{<cond>}{S} <Rd>, <operador_de_deslocamento> <Rm>, ROR #<shift_imm> - Conteúdo de Rm é rotacionado à direita por um valor imediato <Rm>, ROR <Rs> - Conteúdo de Rm é rotacionado à direita por uma quantidade Rs <Rm>, RRX – Conteúdo de Rm é deslocado um bit à direita, o bit de Carry é deslocado para alto e o bit 0 de Rm é colocado no bit de carry As operações de deslocamento não mudam o conteúdo de Rm, apenas o valor do resultado usado como o operando Operador de deslocamento: Formas do ROR 4 bits Imediato 8 bits imediato Pode rotacionar à direita um valor imediato de 8 bits por qualquer número par de bits O campo de rotação tem seu valor multiplicado por dois, dessas forma é possível deslocar até 30 bits Apenas valores especiais de constantes podem ser representados 0xFF (possível) 0xFF0 (possível) 0xFF1 (Não) 0x1FE (Não) Pode ser necessário formar constantes em mais de uma etapa, por exemplo: 0xFFFF Evite, se for possível, criar espaços para dados constantes Formato do operador_de_deslocamento #<immediate> mov add mov ldr R0, #127 R0, R1, #0x7f R2, #125952 R2, =125951 ;R0 = 127 ;R0 = R1 + 127 ;R2 = 125952 = 123*4*4*4*4*4 ;R2 = 125951 = 123*4*4*4*4*4 - 1 LDR R2, =125951 é uma instrução que é desdobrada pelo montador em um carregamento (LDR) de uma posição de memória com o valor 125951 mov add mov R0, #257 R0, R1, #0x101 R2, #125951 ;erro! ;erro! ;125951 = 123*4*4*4*4*4-1 erro! Formato típico de uma instrução LDR/STR LDR{<cond>} <Rd>, <modo_de_endereçamento> LDR - Usado para carregar um valor da memória para um registrador STR - Usado para armazenar o valor em um registrador na memória Pode ser executado condicionalmente Não altera os bits de estados CSPR (Current Status Program Register) Usa modo_de_endereçamento para endereçar o dado na memória Formato do modo_de_endereçamento LDR{<cond>} <Rd>, <modo_de_endereçamento> [<Rn>,#+/-<offset_12>] – O endereço é o conteúdo de Rn mais ou menos o offset_12 de 12 bits com sinal [<Rn>,+/-<Rm>] – Endereço é o conteúdo de Rm mais ou menos o conteúdo de Rm [<Rn>,+/-<Rm>, <shift> <shift_imm>] – O endereço é o conteúdo de Rn mais ou menos conteúdo de Rm deslocado por um operador <shift> pelo número de bits em <shift_imm> [<Rn>,#+/-<offset_12>]! – O endereço é o conteúdo de Rn mais ou menos um offset_12 de 12 bits com sinal, endereço é escrito de volta para Rn (pré-indexado) Formato do modo_de_endereçamento LDR{<cond>} <Rd>, <modo_de_endereçamento> [<Rn>, +/- <Rm>]! – O endereço é o conteúdo de Rn mais ou menos o conteúdo de Rm, endereço é escrito de volta para Rn (pré-indexado) [<Rn>, +/-<Rm>, <shift> <shift_imm>]! – O endereço é o conteúdo de Rn mais ou menos o conteúdo de Rm deslocado por um operador <shift> pelo número de bits em <shift_imm>, o endereço é escrito de volta para Rn (pré-indexado) [<Rn>], #+/-<offset_12> - O endereço é o conteúdo de Rn, o valor offset_12 de 12 bits com sinal é adicionado a Rn, escrito de volta para Rn (pós-indexado) Formato do modo_de_endereçamento LDR{<cond>} <Rd>, <modo_de_endereçamento> [<Rn>] +/-<Rm> - O endereço é o conteúdo de Rn, Rm é adicionado ou subtraído de Rn, escrito de volta para Rn (pós-indexado) [<Rn>] +/-<Rm>, <shift> <shift_imm> - O endereço é o conteúdo de Rn, conteúdo de Rm é deslocado operador <shift> pelo número de bits em <shift_imm> o valor é adicionado ou subtraído de Rn (pósindexado) Formato do modo_de_endereçamento ;Coloca o valor apontado por R0 em R1 e incrementa R0 para ;indicar o próximo inteiro de 32 bits ldr R1, [R0], #4 ;Incrementa o conteúdo de R2 fazendo-o apontar para o próximo ;inteiro de 32 bits, esse inteiro é atribuído para R3 ldr R3, [R2, #4]! ;O endereço é o valor R5 deslocado 4 bytes, o resultado aponta ;para o próximo inteiro de 32 bits, esse inteiro é atribuído para R4. ;R5 não é alterado ldr R4, [R5, #4] ;O mesmo endereçamento pode ser usado pela instrução STR str R1, [R0], #4 str R3, [R2, #4]! str R4, [R5, #4] Instruções Dignas de Atenção BIC – Bit Clear desliga os bits indicados na mascara CLZ – Count Leading Zeros ideal para determinar o número de bits necessários para representar um número LDM/STM – Load/Store Multiple usado para salvar e recuperar registradores na pilha especialmente útil: STMDB, LDMDB RSB – Reverse Substract Operand Order permitem que o operador_de_deslocamento seja subtraídos do registrador Registradores R0 até R3 – Propósito geral, salvos pelo chamador da rotina R4 até R11 – Propósito geral, salvos pela rotina chamada R12 – Frame Pointer (algumas vezes) R13 – Stack Pointer (SP – Apontador da Pilha) R14 – Link Register (LR) R15 – Program Counter (PC) CSPR – Currente Status Program Register, contém a sinalização de overflow, zero, underflow, ... Registradores Pode ser desejável dar nomes mais sugestivos aos registradores, para isso usa-se a diretiva RN contador RN R0 resultado RN R1 Pode ser desejável também colocar nomes as constantes literais (valores imediatos) mascara EQU 0x000000ff minimo EQU 0 maximo EQU 32767 Usando: mov contador, #minimo cmp contador, #maximo Convenção da Pilha A Pilha cresce “para baixo” Topo SP Dados colocados na pilha têm endereços de memória menores que os dados colocados previamente SP + 4 O armazenamento dos dados na pilha é pré-decrementado. SP + 12 Ponteiro da pilha aponta para o último elemento colocado nela. SP + 8 SP + 16 Crescimento da pilha Assembly: Estrutura do Arquivo ;cpenable.asm Comentários se iniciam com ; Informações sobre a área que será usada AREA CPENABLE, CODE, READONLY EXPORT é usado para exportar o nome da função para uso externo Rótulos começam na coluna 1, PROC é uma diretiva usada para identificar funções ;CP15 Routines EXPORT GetCP0Enable ; Retorna valor escrito em r0 GetCP0Enable PROC MRC p15, 0x0, r0, c15, c1, 0x0 mov pc,lr ENDP END END indica onde o arquivo acaba ENDP mostra onde a função acaba Chamando um Módulo em Assembly Por convenção no C para o ARM os quatro primeiros parâmetros são passados nos registradores R0 – R3 O restante é passado na pilha, da esquerda para a direita, sendo que o parâmetro mais à esquerda está no topo da pilha O registrador Stack Pointer aponta para o 5º parâmetro se ele estiver presente Ajustes são necessários se a função não for uma função folha Para funções folha, podemos usar o stack pointer tanto para pilha quanto para frame pointer Para funções não folha o stack pointer será ajustado, ou usa-se R12 como frame pointer ou ajusta-se as referências das variáveis colocadas na pilha pelo chamador Exemplo da Manipulação de Parâmetros em uma função Asm Parâmetros de a-d estão em R0-R3, respectivamente O parâmetro e é o mais baixo na pilha, o ponteiro da pilha stack pointer aponta para ele. Agora, ele está em R4. ; parms.asm AREA PARMS, CODE, READONLY ;Routines Salva os EXPORT Parms ;Parms (a,b,c,d,e,f) registradores R4 Parms PROC e R5 na pilha stmdb sp, {r4,r5} ldr r4, [sp] ldr r5, [sp, #4] Parâmetro f é o ; o código vai aqui ldmdb sp, {r4,r5} próximo na pilha, mov pc,lr assim, stack ENDP pointer+4 é END atribuido para R5 Retornando da Função em Assembly Restaura os valores de R4 e R5, ajuste o stack pointer se necessário Restaura o PC com o conteúdo do Link Register. Lembre-se de salvar o conteúdo de LR se esta função não for uma função folha ;count.asm AREA PARMS, CODE, READONLY ;Routines EXPORT Count Count PROC stmdb sp, {r4,r5} mov r4, #0 mov r5, #0xF8 loop Valores de add r4,r4,#1 retorno da cmp r4,r5 blt loop função são passados em R0 mov r0,r4 ldmdb sp, {r4,r5} mov pc,lr ENDP END Exemplo da Manipulação de Parâmetros em uma função Asm Soma os dois parâmetros e retorna o resultado pela função ;soma2.asm AREA SOMA, CODE, READONLY ;Routinas EXPORT soma2 ;int soma2(int, int) soma2 PROC add R0, R0, R1 ;R0 = R0 + R1 mov pc,lr ENDP END Retornando da Função em Assembly Soma os seis parâmetros e retorna o resultado pela função ;soma6.asm AREA SOMA, CODE, READONLY ;Routinas EXPORT soma6 ;int soma6(int, int, int, int, int, int) soma6 PROC stmdb add add add ldr ldr add add ldmdb mov ENDP END SP, {R4,R5} R0, R0, R1 R1, R2, R3 R0, R0, R1 R4, [SP] R5, [SP, #4] R1, R4, R5 R0, R0, R1 SP, {R4,R5} PC, LR Retornando da Função em Assembly Soma os seis parâmetros e retorna o resultado no sétimo parâmetro Observe que os registradores R4 e R5 não estão mais sendo usados. ;soma7.asm AREA SOMA, CODE, READONLY ;Routinas EXPORT soma7 ;int soma7(int, int, int, int, int, int, int *) soma7 PROC add add add ldr ldr add add ldr str mov ENDP END R0, R0, R1 R1, R2, R3 R0, R0, R1 R1, [SP] R2, [SP, #4] R1, R1, R2 R0, R0, R1 R1, [SP, #8] R0, [R1] PC, LR Retornando da Função em Assembly AREA SOMA, CODE, READONLY EXPORT soma7 ;int soma7(int, int, int, int, short int, short int, short int *) soma7 Soma os seis parâmetros e retorna o resultado no sétimo parâmetro PROC stmdb add add add ldrh ldrh add add ldr strh ldmdb mov ENDP END SP, {R4, R5, R6} R0, R0, R1 R1, R2, R3 R0, R0, R1 R4, [SP] R5, [SP, #4] R1, R4, R5 R0, R0, R1 R5, [SP, #8] R0, [R5] SP, {R4, R5, R6} PC, LR Observe que os três últimos parâmetros têm 16 bits cada Passando vetor por parâmetro em Assembly Retorna o endereço de um elemento em um vetor int vet[10]; int p*; int * procura(int v[ ], int x) { int i; for (i=0; i<10; i++) if (v[i]==x) return &v[i]; return NULL; } void main(void) { .. . p=procura(vet, 1234); .. } procura AREA VETOR, CODE, READONLY EXPORT procura PROC mov R2, #0 laco ldr cmp addeq moveq add cmp movge movge b ENDP END R3, [R0, R2, LSL #2] R1, R3 R0, R0, R2 PC, LR ;return &v[i] R2, #1 R2, #10 R0, #0 ;return NULL PC, LR laco Passando ponteiro por parâmetro em Assembly Iniciação de uma lista linear simplesmente encadeada typedef struct s_no { long dado; struct s_no *prox; } no; &lista, d R0 d R0 lista, *d [R0] ? antes *d [R0] depois no *lista; void iniciaLista(no *d=NULL; } void main(void) { .. . iniciaLista(&lista) .. . } **d) { AREA LISTA, CODE, READONLY EXPORT iniciaLista iniciaLista PROC mov R1, #0 str R1, [R0] mov PC, LR ENDP END Exportando uma função Assembly de uma DLL AREA .drectve DRECTVE DCB “-export:Count” DCB é usado para definir uma string na qual contém diretivas para o ligador ajustar o código como sendo uma exportação .dll A diretiva deve estar na área DRECTVE em uma seção chamada .drectve ;count.asm AREA COUNT,CODE,READONLY ; Rotinas EXPORT Count Count PROC stmdb sp, {r4,r5} mov r4, #0 mov r5, #0xF8 loop add r4, r4,#1 cmp r4, r5 blt loop mov r0, r4 ldmdb sp, {r4,r5} mov pc, lr ENDP END Usando Assembly na IDE Clique na pasta Source files, adicione um arquivo .asm no projeto Selecione ‘All Files’ na caixa de diálogo, assim você pode ver os arquivos .asm Clique com o botão direito no arquivo ASM na pasta source do projeto, vá para settings Na aba Custom Build Digite armasm para chamar o montador Utilize a opção xscale cpu se estiver chamando mnemônicos xscale (armasm –cpu xscale) Use o caminho relativo ao arquivo como o apresentado no arquivo de entrada (input File) Use apenas o nome do arquivo com a extensão .obj como saída Melhorando as Dependências Usando LDR add r1, r2, r3 ldr r0, [r5] add r6, r0, r1 sub r8, r2, r3 mul r9, r2, r3 ldr r0, [r5] add r1, r2, r3 sub r8, r2, r3 add r6, r0, r1 mul r9, r2, r3 Com o dado na Cache, três ciclos são necessários para a transferência do valor ao registrador Como o dado pode não estar na cache, o LDR deve ser colocado o mais cedo possível antes do ponto de utilização Instruções LDRD/STRD Lê e armazena 64 bits em dois registradores O Endereço deve estar alinhado em 8 bytes (64bits) Os registradores devem ser os pares: R0,R1; R2,R3; R4,R5; ... add sub ldrd orr mul r6, r5, r0, r8, r7, r7, r6, [r3] r1, r0, r8 r9 #0xf r7 ldrd add sub mul orr r0, r6, r5, r7, r8, [r3] r7, r6, r0, r1, r8 r9 r7 #0xf LDRD necessita de 3 ciclos para disponibilizar os dados nos registradores Instruções LDM/STM 2 a 20 ciclos para disponibilizar os valores nos registradores, depende do número de registradores envolvidos na troca de dados A latência é de 2 ciclos mais 1 ciclo por registrador envolvido A próxima instrução é atrasada um ciclo (com ou sem dependências e acessos à memória) Utilizando LDM ou LDRD Soma de dois valores de 64 bits r0 endereço do primeiro valor r1 endereço do segundo valor ldm r0, {r2, r3} ldm r1, {r4, r5} adds r0, r2, r4 adc r1, r3, r5 ldrd r2, [r0] ldrd r4, [r1] adds r0, r2, r4 adc r1, r3, r5 Mais rápido para executar Indo além do básico em Assembly Estude o ARM ARM (ARM Architecture Reference Manual), também disponível no formato pdf em www.arm.org Estude o help do EVC++, tópico ARM Assembly Compile seu código com a opção de disassembly ativada e veja o que o compilador gera: Misture código C com assembly Desligue as otimizações do compilador Indo além do básico em Assembly: Use o eVC++ Vá em Project Settings-> C/C++ tab -> Selecione Listing Files Coloque List Assembly with Source Quando e onde substituir o código por Assembly Apenas em regiões do código críticas no desempenho Para tirar maior proveito do pipeline do processador Intel® XScale™ O número de variáveis envolvidas for grande O compilador é incapaz de fazer um bom uso de muitos registradores O compilador está armazenando valores locais em memória Quando esses valores poderiam ser mantidos nos registradores Há instruções muito melhores em Assembly para: Melhor acesso a overflow Melhor acesso a instruções DSP Quando for escrever o código em Assembly Utilize muitos comentários Inclua o código em C, que o Assembly estiver substituindo, como comentário Use as diretivas RN e EQU para dar nomes esclarecedores aos registradores e aos valores imediatos Sempre salve e recupere os conteúdos dos Registradores usados pela função que faz a chamada se você os usa Cuidado ao trabalhar com funções que não são do tipo folha Salve o conteúdo de LR Observe as alterações em SP Quando for escrever o código em Assembly Tome cuidado com o alinhamento de dados Para ler dados do tipo short em um vetor use as instruções LDRH ou LDRSH Use STRH para escrever dados do tipo short em um vetor Assim como com os tipos short, tenha certeza de estar usando LDRB para vetores de bytes Desconsiderar essas instruções para dados do tipo short ou byte pode causar problemas de alinhamento de dados Erros de alinhamento em geral causam o travamento da máquina, deixando-o sem pistas do que pode ter dado errado com o seu programa Otimizando Código em Assembly Não armazena dados de volta a pilha Mantenha as variáveis em registradores o máximo de tempo possível Observe a tabela de latências Ordene as instruções tendo em mente o tempo de latência de cada uma delas Tente desenrolar o laço Tente usar PreLoad também em Assembly (Instrução PLD) Lembre-se que algumas instruções aritméticas levam mais de 1 ciclo de clock Não deixe de tirar proveito das instruções de execução condicional Repita pequenos pedaços de código se isso conseguir evitar desvios (branches) Modulo VI Técnicas de Otimização Considerações sobre o código Compilado Evite o uso desnecessário de Shorts int Test(void) { short x, y; x = 3; y = 4; Observe que o compilador realiza dois deslocamentos para estender o sinal do valor de ShortSum para retornar o valor de Test. Perda de tempo! ; depois de retornar de ShortSum mov r1,r0,lsl #16 mov r0,r1,#asr #16 mov pc,lr ENDP ShortSum PROC ; start return ShortSum(x, y); } mov r0,r0,lsl #16 mov r2,r0,asr #16 mov r1,r1,lsl #16 short ShortSum(short a, short b) add r0,r2,r1,asr #16 { mov r2,r0,lsl #16 return a+b; } Diversos mov’s para truncar e estender o sinal dos operandos e do resultado mov r0,r2,asr #16 mov pc,lr ; return ENDP Corrija as suas variáveis shorts int Test(void) { int x,y; Se necessário manter o mesmo range de short, use cast ;Depois de retornar de ShotSum mov r1,r0,lsl #16 mov r0,r1,#asr #16 x = 3; mov pc,lr y = 4; ENDP return (short)ShortSum(x, y); } ShortSum PROC ; start add r0,r0,r1 int ShortSum(int a, int b) { mov pc,lr ; return return a+b; } ENDP Evite o Apelidamento de Variáveis [Aliasing of Variables] int AliasTaken(int indx) { TransIndex(&indx); indx += 5; indx += DisplaceIndex(indx); indx += DitherIndex(indx); return indx; } Já que usamos o endereço de indx, o compilador é conservador em mantê-lo em um registrador, causando vários acessos à memória ; TransIndex(&indx) add r0, sp,#8 bl TransIndex ; indx += 5; ldr r3, [sp, #8] add r0, r3, #5 str r0, [sp, #8] ;indx += DisplaceIndex(indx); bl DisplaceIndex ; 00000028 mov r3, r0 ldr r0, [sp, #8] add r0, r0, r3 str r0, [sp, #8] ; indx += DitherIndex(indx); bl DitherIndex ; 0000003C mov r3, r0 ldr r0, [sp, #8] add r0, r0, r3 Aperfeiçoando o Código int AliasTaken(int indx) ; ref = indx; mov r4, r0 ; TransIndex(&ref); { add r0, sp, #0 inf ref = indx; str r4, [sp] TransIndex(&ref); bl TransIndex indx += 5; indx += DisplaceIndex(indx); ; indx += 5; add r4, r4, #5 indx += DitherIndex(indx); ; indx += DisplaceIndex(indx); return indx; mov r0, r4 bl DisplaceIndex ; 00000028 add r4, r4, r0 } Observe que a variável indx é mantida agora em r4, sem exigir repetidos STR/LDR para [sp, #0] ; indx += DitherIndex(indx); mov r0, r4 bl DitherIndex ; 00000034 add r0, r0, r4 Módulo VI Laboratório