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